switch-profile 2.4.0 → 2.5.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 +6 -2
- package/dist/cli.js +281 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ npx switch-profile
|
|
|
21
21
|
│ ○ globex-dev ReadOnly 333333333333 us-east-1
|
|
22
22
|
│ ○ initech-dev Admin 444444444444 eu-west-1
|
|
23
23
|
│
|
|
24
|
-
│ [Log in] [Add] [Rename] [Remove] [Log out] [Settings]
|
|
24
|
+
│ [Log in] [Add] [Portals] [Rename] [Remove] [Log out] [Settings]
|
|
25
25
|
└ ↑↓ choose · enter switch · tab actions · esc quit
|
|
26
26
|
```
|
|
27
27
|
|
|
@@ -100,8 +100,9 @@ No profiles yet? `switch-profile` starts the **Add profiles** flow. To import ev
|
|
|
100
100
|
Running `sp` (or `npx switch-profile`) with no arguments opens the home screen:
|
|
101
101
|
|
|
102
102
|
- **Type and press Enter** to switch. That's the whole path for everyday use.
|
|
103
|
-
- **Tab** moves to the action bar below the list, which is always visible however many profiles you have: **Log in**, **Add**, **Rename**, **Remove**, **Log out**, **Settings**. Use ←→ to choose and Enter to open.
|
|
103
|
+
- **Tab** moves to the action bar below the list, which is always visible however many profiles you have: **Log in**, **Add**, **Portals**, **Rename**, **Remove**, **Log out**, **Settings**. Use ←→ to choose and Enter to open.
|
|
104
104
|
- **Every action opens as a page and comes back** to the home screen when it's done. **Esc** goes back from a page, and quits from the home screen.
|
|
105
|
+
- **When something fails, it stops and tells you.** An error screen says what failed, what AWS answered, why, and which SSO portal (start URL and region) was involved. Then it asks what to do: fix the portal's region in one step, edit the portal, try again, or go back.
|
|
105
106
|
- A switch ends the session, which is what lets `sp` set `AWS_PROFILE` in your terminal.
|
|
106
107
|
|
|
107
108
|
## Commands
|
|
@@ -117,6 +118,7 @@ Running `sp` (or `npx switch-profile`) with no arguments opens the home screen:
|
|
|
117
118
|
| `add --from-sso [session]` | Imports or re-syncs every account and role of an `[sso-session]`. Options: `--region`, `--prefix`, `--prune`, `--yes`. |
|
|
118
119
|
| `rename [profile] [new-name]` | Renames a profile (alias `mv`), for example an imported `acme-prod-workloads-administratoraccess` to `acme-prod`. Also updates `source_profile` references, the default profile and the home screen order. SSO logins are kept. Scripts, terminals and tools that use the old name need the new one. |
|
|
119
120
|
| `remove [profiles...] [--yes]` | Removes profiles (alias `rm`). The current default cannot be removed. |
|
|
121
|
+
| `portals [name]` | Lists your SSO portals (`[sso-session]`) and edits one in place: start URL and SSO region (alias `portal`). Flags a portal whose region differs from the one its portal page names, and fixes it in one step. `--region` / `--start-url` edit without prompting; `--json` lists them (`--check` adds the region read from each portal). |
|
|
120
122
|
| `settings` | Per-terminal switching, SSO login mode, legacy SSO upgrade. |
|
|
121
123
|
|
|
122
124
|
Global options: `--json`, `--no-input` (never prompt), `--debug` (show stack traces), `-v, --version`. Every run, AWS CLI call and error is logged to `~/.switch-profile/switch-profile.log` (secrets redacted). In examples, `sp` and `switch-profile` are interchangeable; only `sp` changes the current terminal.
|
|
@@ -242,6 +244,8 @@ The SSO region is wrong. It must be the region of your IAM Identity Center insta
|
|
|
242
244
|
|
|
243
245
|
The start URL is usually fine: AWS sends this error when the **SSO region** is wrong. Use the region where IAM Identity Center lives (Identity Center console › Settings), not where you deploy. If the portal is already set up, type its existing session name instead (`sp add` lists them with their regions).
|
|
244
246
|
|
|
247
|
+
When importing from a portal (`sp add` > Accounts from an SSO portal) or switching to one of its profiles, the same mistake shows as `Invalid request` during the login. `switch-profile` reads the portal's real region from its start page and offers to fix the saved portal. You can also check and fix every portal any time with `sp portals` (or **Portals** on the home screen).
|
|
248
|
+
|
|
245
249
|
### Something failed and the message is not enough
|
|
246
250
|
|
|
247
251
|
Open `~/.switch-profile/switch-profile.log`. It has every run, every AWS CLI call with its exit code and output, and every error with its full chain. Secrets are redacted, so you can share it.
|
package/dist/cli.js
CHANGED
|
@@ -5419,7 +5419,7 @@ var require_package = __commonJS({
|
|
|
5419
5419
|
"package.json"(exports2, module2) {
|
|
5420
5420
|
module2.exports = {
|
|
5421
5421
|
name: "switch-profile",
|
|
5422
|
-
version: "2.
|
|
5422
|
+
version: "2.5.0",
|
|
5423
5423
|
main: "index.js",
|
|
5424
5424
|
bin: {
|
|
5425
5425
|
"switch-profile": "index.js"
|
|
@@ -5777,9 +5777,17 @@ var require_transforms = __commonJS({
|
|
|
5777
5777
|
["sso_region", region],
|
|
5778
5778
|
["sso_registration_scopes", SSO_SCOPES],
|
|
5779
5779
|
[VERSION_KEY, version]
|
|
5780
|
-
])
|
|
5780
|
+
]), updateSsoSession = (configStr, { name, startUrl, region, version }) => {
|
|
5781
|
+
let section = `sso-session ${name}`;
|
|
5782
|
+
return ini.getSection(configStr, section) ? ini.setKeys(configStr, section, {
|
|
5783
|
+
...startUrl ? { sso_start_url: startUrl } : {},
|
|
5784
|
+
...region ? { sso_region: region } : {},
|
|
5785
|
+
[VERSION_KEY]: version
|
|
5786
|
+
}) : configStr;
|
|
5787
|
+
};
|
|
5781
5788
|
module2.exports = {
|
|
5782
5789
|
GENERATED_KEY,
|
|
5790
|
+
updateSsoSession,
|
|
5783
5791
|
ACCOUNT_NAME_KEY,
|
|
5784
5792
|
generatedProfileName,
|
|
5785
5793
|
populateSsoProfiles,
|
|
@@ -5817,13 +5825,22 @@ var require_login = __commonJS({
|
|
|
5817
5825
|
text2 = text2 || "";
|
|
5818
5826
|
let urls = text2.match(URL_REGEX) || [], code = (text2.match(CODE_REGEX) || [])[1] || null, completeUrl = urls.find((u5) => /user_code=/.test(u5)) || null;
|
|
5819
5827
|
return { url: urls.find((u5) => u5 !== completeUrl) || completeUrl || null, code, completeUrl };
|
|
5820
|
-
}, WRONG_REGION = /Invalid start url|invalid_request|InvalidRequestException/i,
|
|
5828
|
+
}, WRONG_REGION = /Invalid start url|invalid_request|InvalidRequestException/i, isWrongSsoRegion = (output) => WRONG_REGION.test(output || ""), PORTAL_REGION = /portal\.sso\.([a-z]{2}(?:-[a-z]+)+-\d+)\.amazonaws\.com/, portalRegionFromHeaders = (values) => {
|
|
5829
|
+
for (let v of values || []) {
|
|
5830
|
+
let m3 = (v || "").match(PORTAL_REGION);
|
|
5831
|
+
if (m3)
|
|
5832
|
+
return m3[1];
|
|
5833
|
+
}
|
|
5834
|
+
return null;
|
|
5835
|
+
}, configureSsoFailure = (output, sessions) => {
|
|
5821
5836
|
let lines = (output || "").split(`
|
|
5822
5837
|
`).map((l2) => l2.trim()).filter(Boolean), detail = (lines.filter((l2) => /error/i.test(l2)).slice(-1)[0] || lines.slice(-1)[0] || "").replace(/^aws:\s*\[ERROR\]:\s*/i, ""), description = ((output || "").match(/error_description:\s*(.+)/) || [])[1], known = (sessions || []).filter((s) => s.sso_region).map((s) => `${s.name} (${s.sso_region}, ${s.sso_start_url})`), reuse = known.length ? ` Portals already set up here: ${known.join(", ")}. Type one of these names as the session name to reuse it.` : "", message = `aws configure sso failed${description || detail ? `: ${(description || detail).trim()}` : "."}`;
|
|
5823
|
-
return
|
|
5838
|
+
return isWrongSsoRegion(output) ? { message, hint: `The SSO region is most likely wrong: it must be the region where IAM Identity Center lives (Identity Center console \u203A Settings), not where you deploy. AWS reports a wrong region as an invalid start URL.${reuse}` } : { message, hint: `Run sp add to try again.${reuse}` };
|
|
5824
5839
|
};
|
|
5825
5840
|
module2.exports = {
|
|
5826
5841
|
configureSsoFailure,
|
|
5842
|
+
isWrongSsoRegion,
|
|
5843
|
+
portalRegionFromHeaders,
|
|
5827
5844
|
isRemoteSession,
|
|
5828
5845
|
resolveLoginMode,
|
|
5829
5846
|
loginFlags,
|
|
@@ -5944,7 +5961,7 @@ var require_regions = __commonJS({
|
|
|
5944
5961
|
// src/aws/index.js
|
|
5945
5962
|
var require_aws = __commonJS({
|
|
5946
5963
|
"src/aws/index.js"(exports2, module2) {
|
|
5947
|
-
var fs = require("fs"), crypto = require("crypto"), { homedir, EOL } = require("os"), { join: join2, dirname: dirname2, basename } = require("path"), { exec: exec2, run: run2, isCommandExist, catchErrors, wrapErrors } = require_core(), settings2 = require_settings(), { CLI_VERSION: CLI_VERSION2 } = settings2, transforms = require_transforms(), { loginFlags, isRemoteSession, atLeast } = require_login(), regions = require_regions(), log3 = require_log(), IS_WINDOWS = process.platform === "win32", NL = IS_WINDOWS ? `
|
|
5964
|
+
var fs = require("fs"), crypto = require("crypto"), { homedir, EOL } = require("os"), { join: join2, dirname: dirname2, basename } = require("path"), { exec: exec2, run: run2, isCommandExist, catchErrors, wrapErrors } = require_core(), settings2 = require_settings(), { CLI_VERSION: CLI_VERSION2 } = settings2, transforms = require_transforms(), { loginFlags, isRemoteSession, atLeast, portalRegionFromHeaders } = require_login(), regions = require_regions(), log3 = require_log(), IS_WINDOWS = process.platform === "win32", NL = IS_WINDOWS ? `
|
|
5948
5965
|
` : EOL, AWS_CONFIG_FILE = join2(homedir(), ".aws", "config"), AWS_CREDS_FILE = join2(homedir(), ".aws", "credentials"), AWS_SSO_FOLDER = join2(homedir(), ".aws", "sso", "cache"), awsExists = isCommandExist("aws"), _awsCliVersion = null, whichAws = () => exec2(`${process.platform == "win32" ? "where" : "which"} aws`).then((out) => (out || "").split(/\r?\n/)[0].trim(), () => null), getAwsCliVersion = async () => {
|
|
5949
5966
|
if (_awsCliVersion)
|
|
5950
5967
|
return _awsCliVersion;
|
|
@@ -6047,6 +6064,18 @@ var require_aws = __commonJS({
|
|
|
6047
6064
|
}, readSsoToken = async ({ ssoSession, startUrl }) => {
|
|
6048
6065
|
let key = crypto.createHash("sha1").update(ssoSession || startUrl).digest("hex"), token = await fs.promises.readFile(join2(AWS_SSO_FOLDER, `${key}.json`), "utf8").then(JSON.parse).catch(() => null);
|
|
6049
6066
|
return !token || !token.accessToken || new Date(token.expiresAt).getTime() < Date.now() + 60 * 1e3 ? null : token;
|
|
6067
|
+
}, detectSsoRegion = async (startUrl, { timeoutMs = 5e3 } = {}) => {
|
|
6068
|
+
let started = Date.now();
|
|
6069
|
+
try {
|
|
6070
|
+
let url = new URL(startUrl);
|
|
6071
|
+
url.hash = "";
|
|
6072
|
+
let res = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(timeoutMs) });
|
|
6073
|
+
await res.body?.cancel();
|
|
6074
|
+
let region = portalRegionFromHeaders([res.headers.get("link"), res.headers.get("content-security-policy")]);
|
|
6075
|
+
return log3.info(`SSO region of ${url} \u2192 ${region || "not found"} (HTTP ${res.status} in ${Date.now() - started}ms)`), region;
|
|
6076
|
+
} catch (err) {
|
|
6077
|
+
return log3.tolerated(`SSO region lookup of ${startUrl}`, null)(err);
|
|
6078
|
+
}
|
|
6050
6079
|
}, listSsoAccounts = async (accessToken, region) => {
|
|
6051
6080
|
let data = await run2("aws", ["sso", "list-accounts", "--access-token", accessToken, "--region", region, "--output", "json"]);
|
|
6052
6081
|
return (JSON.parse(data).accountList || []).map((a2) => ({ accountId: a2.accountId, accountName: a2.accountName }));
|
|
@@ -6156,6 +6185,7 @@ var require_aws = __commonJS({
|
|
|
6156
6185
|
awsLogin,
|
|
6157
6186
|
logoutAll,
|
|
6158
6187
|
readSsoToken,
|
|
6188
|
+
detectSsoRegion,
|
|
6159
6189
|
listSsoAccounts,
|
|
6160
6190
|
listSsoRoles,
|
|
6161
6191
|
getSsoLoginStatus,
|
|
@@ -6180,7 +6210,7 @@ var require_aws = __commonJS({
|
|
|
6180
6210
|
// src/shell.js
|
|
6181
6211
|
var require_shell = __commonJS({
|
|
6182
6212
|
"src/shell.js"(exports2, module2) {
|
|
6183
|
-
var fs = require("fs"), { homedir } = require("os"), { join: join2, dirname: dirname2, basename } = require("path"), { run: run2 } = require_core(), log3 = require_log(), IS_WINDOWS = process.platform === "win32", IS_MAC = process.platform === "darwin", FUNCTION_NAME = "sp", BLOCK_START_PREFIX = "# >>> switch-profile", BLOCK_END = "# <<< switch-profile <<<", BLOCK_REGEX = /(^|\r?\n)# >>> switch-profile[^\n]*\r?\n[\s\S]*?# <<< switch-profile <<<[^\n]*(\r?\n)?/, blockStart = (version) => `${BLOCK_START_PREFIX} v${version} (managed by switch-profile, do not edit) >>>`, CLEARED_VARS = ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", "AWS_DEFAULT_PROFILE"], SUBCOMMANDS = ["status", "login", "logout", "add", "remove", "settings", "use", "rename"], PROFILES_SED = "sed -n 's/^\\[profile \\(.*\\)\\]$/\\1/p'", POSIX_FUNCTION = (shell) => `${FUNCTION_NAME}() {
|
|
6213
|
+
var fs = require("fs"), { homedir } = require("os"), { join: join2, dirname: dirname2, basename } = require("path"), { run: run2 } = require_core(), log3 = require_log(), IS_WINDOWS = process.platform === "win32", IS_MAC = process.platform === "darwin", FUNCTION_NAME = "sp", BLOCK_START_PREFIX = "# >>> switch-profile", BLOCK_END = "# <<< switch-profile <<<", BLOCK_REGEX = /(^|\r?\n)# >>> switch-profile[^\n]*\r?\n[\s\S]*?# <<< switch-profile <<<[^\n]*(\r?\n)?/, blockStart = (version) => `${BLOCK_START_PREFIX} v${version} (managed by switch-profile, do not edit) >>>`, CLEARED_VARS = ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", "AWS_DEFAULT_PROFILE"], SUBCOMMANDS = ["status", "login", "logout", "add", "remove", "settings", "use", "rename", "portals"], PROFILES_SED = "sed -n 's/^\\[profile \\(.*\\)\\]$/\\1/p'", POSIX_FUNCTION = (shell) => `${FUNCTION_NAME}() {
|
|
6184
6214
|
local __sp_file __sp_status
|
|
6185
6215
|
__sp_file="$(mktemp "\${TMPDIR:-/tmp}/switch-profile.XXXXXX")" || return 1
|
|
6186
6216
|
if [ -n "\${SWITCH_PROFILE_DEV_BIN:-}" ]; then
|
|
@@ -6496,9 +6526,15 @@ var require_common = __commonJS({
|
|
|
6496
6526
|
return { text: null, needsLogin: !1 };
|
|
6497
6527
|
let [, login] = await aws.getSsoLoginStatus(profile.sso_start_url), at = (state && state.settings.logins || {})[loginKey(profile)], since = at ? ui2.dim(`logged in ${ui2.ago(at)}`) : null, s = login ? login.state : "none";
|
|
6498
6528
|
return s == "refreshable" ? { text: ui2.join([ui2.ok(`SSO ${ui2.sym.ok}`) + " auto-refresh", since]), needsLogin: !1 } : s == "active" ? { text: ui2.join([ui2.ok(`SSO ${ui2.sym.ok}`) + ` ${Math.round((login.expiresAt - Date.now()) / 6e4)} min left`, since]), needsLogin: !1 } : { text: ui2.warn(s == "expired" ? "SSO login expired" : "not logged in"), needsLogin: !0 };
|
|
6499
|
-
}
|
|
6529
|
+
}, chooseRegion = async (message, initialValue) => ask(p4.autocomplete({
|
|
6530
|
+
message,
|
|
6531
|
+
maxItems: 8,
|
|
6532
|
+
initialValue,
|
|
6533
|
+
options: aws.regions.map((r2) => ({ value: r2.code, label: r2.code, hint: r2.name }))
|
|
6534
|
+
}));
|
|
6500
6535
|
module2.exports = {
|
|
6501
6536
|
CancelError: CancelError2,
|
|
6537
|
+
chooseRegion,
|
|
6502
6538
|
ask,
|
|
6503
6539
|
preflight,
|
|
6504
6540
|
upgradeLegacySso,
|
|
@@ -7513,7 +7549,7 @@ var require_clipboard = __commonJS({
|
|
|
7513
7549
|
// src/commands/login-flow.js
|
|
7514
7550
|
var require_login_flow = __commonJS({
|
|
7515
7551
|
"src/commands/login-flow.js"(exports2, module2) {
|
|
7516
|
-
var { spawn } = require("child_process"), p4 = (init_dist4(), __toCommonJS(dist_exports2)), { renderUnicodeCompact } = require_dist(), aws = require_aws(), settings2 = require_settings(), { parseLoginOutput, isRemoteSession, atLeast } = require_login(), clipboard = require_clipboard(), ui2 = require_ui(), log3 = require_log(), { CancelError: CancelError2 } = require_common(), IS_WINDOWS = process.platform == "win32", RAW_OUTPUT_AFTER_MS = 1e4, clipboardNote = (result) => result == "local" ? ui2.ok("\u{1F4CB} copied") : result == "terminal" ? ui2.dim("\u{1F4CB} sent to your clipboard") : "", ssoLogin = async (target, { label, loginKey, interactive, loginMode }) => {
|
|
7552
|
+
var { spawn } = require("child_process"), p4 = (init_dist4(), __toCommonJS(dist_exports2)), { renderUnicodeCompact } = require_dist(), aws = require_aws(), settings2 = require_settings(), { parseLoginOutput, isRemoteSession, atLeast, isWrongSsoRegion } = require_login(), clipboard = require_clipboard(), ui2 = require_ui(), log3 = require_log(), { CancelError: CancelError2 } = require_common(), IS_WINDOWS = process.platform == "win32", RAW_OUTPUT_AFTER_MS = 1e4, clipboardNote = (result) => result == "local" ? ui2.ok("\u{1F4CB} copied") : result == "terminal" ? ui2.dim("\u{1F4CB} sent to your clipboard") : "", ssoLogin = async (target, { label, loginKey, interactive, loginMode }) => {
|
|
7517
7553
|
let current = await settings2.read(), args = await aws.ssoLoginArgs(target, loginMode || settings2.getLoginMode(current)), legacyCli = !atLeast(await aws.getAwsCliVersion(), [2, 22, 0]), device = args.includes("--use-device-code") || legacyCli, remote = isRemoteSession(process.env, process.platform);
|
|
7518
7554
|
if (!interactive)
|
|
7519
7555
|
return plainLogin(args, label);
|
|
@@ -7571,12 +7607,12 @@ ${ui2.dim(`If nothing opens, visit: ${parsed.url}`)}`);
|
|
|
7571
7607
|
spin && spin.error("Login did not complete");
|
|
7572
7608
|
let lines = output.trim().split(`
|
|
7573
7609
|
`).map((l2) => l2.trim()).filter(Boolean), detail = lines.filter((l2) => /error|exception|denied|invalid|expired|unable/i.test(l2)).slice(-2).join(`
|
|
7574
|
-
`) || lines.slice(-1)[0] || ""
|
|
7575
|
-
throw new ui2.CliError(`SSO login for ${label} failed.${detail ? `
|
|
7610
|
+
`) || lines.slice(-1)[0] || "", wrongRegion = isWrongSsoRegion(output), error = new ui2.CliError(`SSO login for ${label} failed.${detail ? `
|
|
7576
7611
|
${detail}` : ""}`, {
|
|
7577
7612
|
cause: Object.assign(new Error(`aws ${args.join(" ")} exited with code ${code}`), { output }),
|
|
7578
|
-
hint: /invalid_grant|InvalidGrant/i.test(output) ? "Check the SSO region of this profile: it must be the region of your IAM Identity Center, not where your resources live." : `Try again. Details: ${log3.displayPath()}`
|
|
7613
|
+
hint: wrongRegion ? "The SSO region is most likely wrong: it must be the region where IAM Identity Center lives, not where you deploy. Check and fix it with: sp portals" : /invalid_grant|InvalidGrant/i.test(output) ? "Check the SSO region of this profile: it must be the region of your IAM Identity Center, not where your resources live." : `Try again. Details: ${log3.displayPath()}`
|
|
7579
7614
|
});
|
|
7615
|
+
throw error.wrongRegion = wrongRegion, error.target = target, error;
|
|
7580
7616
|
}, plainLogin = async (args, label) => {
|
|
7581
7617
|
console.error(`Logging in to ${label}. Approve the request using the URL and code below.`), await new Promise((resolve, reject) => {
|
|
7582
7618
|
let started = Date.now(), child = spawn("aws", args, { stdio: ["ignore", process.stderr, process.stderr], ...IS_WINDOWS ? { shell: !0 } : {} });
|
|
@@ -7597,6 +7633,93 @@ ${detail}` : ""}`, {
|
|
|
7597
7633
|
}
|
|
7598
7634
|
});
|
|
7599
7635
|
|
|
7636
|
+
// src/commands/portals.js
|
|
7637
|
+
var require_portals = __commonJS({
|
|
7638
|
+
"src/commands/portals.js"(exports2, module2) {
|
|
7639
|
+
var p4 = (init_dist4(), __toCommonJS(dist_exports2)), aws = require_aws(), transforms = require_transforms(), settings2 = require_settings(), ui2 = require_ui(), { ask, chooseRegion } = require_common(), { ssoLogin } = require_login_flow(), validateStartUrl = (v) => /^https:\/\/\S+$/.test(v || "") ? void 0 : "Enter the https:// start URL of the portal.", validateRegion = (v) => aws.regions.some((r2) => r2.code == v) ? void 0 : `Unknown region ${v}.`, readConfig = async () => {
|
|
7640
|
+
let [errors, configStr] = await aws.getConfigFile();
|
|
7641
|
+
if (errors)
|
|
7642
|
+
throw ui2.cliErrorFrom(errors);
|
|
7643
|
+
return configStr;
|
|
7644
|
+
}, listPortals = async (state, { detect } = {}) => {
|
|
7645
|
+
let portals = transforms.listSsoSessions(await readConfig()).map((s) => ({
|
|
7646
|
+
name: s.name,
|
|
7647
|
+
startUrl: s.sso_start_url || null,
|
|
7648
|
+
region: s.sso_region || null,
|
|
7649
|
+
profiles: state.profiles.filter((x2) => x2.sso_session == s.name).map((x2) => x2.name)
|
|
7650
|
+
}));
|
|
7651
|
+
return detect && await Promise.all(portals.map(async (x2) => {
|
|
7652
|
+
x2.detectedRegion = x2.startUrl ? await aws.detectSsoRegion(x2.startUrl, { timeoutMs: 4e3 }) : null;
|
|
7653
|
+
})), portals;
|
|
7654
|
+
}, findPortal = async (state, name) => {
|
|
7655
|
+
let portal = (await listPortals(state)).find((x2) => x2.name == name);
|
|
7656
|
+
if (!portal)
|
|
7657
|
+
throw new ui2.CliError(`There is no SSO portal named ${name}.`, { code: 3, hint: "List them with: switch-profile portals" });
|
|
7658
|
+
return portal;
|
|
7659
|
+
}, regionMismatch = (region, detectedRegion) => !!(detectedRegion && region && detectedRegion != region), regionNote = ({ region, detectedRegion }) => regionMismatch(region, detectedRegion) ? ui2.join([region, ui2.warn(`${ui2.sym.warn} its portal is in ${detectedRegion}`)]) : detectedRegion ? ui2.join([region, ui2.dim("matches the portal")]) : region || ui2.warn("no region"), portalHint = (x2) => ui2.join([regionNote(x2), `${x2.profiles.length} profile${x2.profiles.length == 1 ? "" : "s"}`, x2.startUrl]), updatePortal = async (name, { startUrl, region }) => {
|
|
7660
|
+
let configStr = await readConfig();
|
|
7661
|
+
await aws.writeAwsFile(aws.AWS_CONFIG_FILE, transforms.updateSsoSession(configStr, { name, startUrl, region, version: settings2.CLI_VERSION }));
|
|
7662
|
+
}, editPortal = async (name, state, { offerLogin = !0, detectedRegion } = {}) => {
|
|
7663
|
+
let changed = !1, detected = detectedRegion;
|
|
7664
|
+
for (; ; ) {
|
|
7665
|
+
let portal = await findPortal(state, name);
|
|
7666
|
+
detected === void 0 && (detected = portal.startUrl ? await aws.detectSsoRegion(portal.startUrl) : null);
|
|
7667
|
+
let mismatch = regionMismatch(portal.region, detected);
|
|
7668
|
+
p4.log.info([
|
|
7669
|
+
ui2.strong(`Portal ${name}`),
|
|
7670
|
+
`${ui2.dim("Start URL ")} ${portal.startUrl || ui2.warn("missing")}`,
|
|
7671
|
+
`${ui2.dim("SSO region")} ${regionNote({ region: portal.region, detectedRegion: detected })}`,
|
|
7672
|
+
detected ? null : ui2.dim(" (could not read the region from the portal page)"),
|
|
7673
|
+
`${ui2.dim("Profiles ")} ${portal.profiles.length ? `${portal.profiles.slice(0, 4).join(", ")}${portal.profiles.length > 4 ? `, +${portal.profiles.length - 4} more` : ""}` : "none"}`
|
|
7674
|
+
].filter(Boolean).join(`
|
|
7675
|
+
`));
|
|
7676
|
+
let choice = await ask(p4.select({
|
|
7677
|
+
message: `Edit ${name}`,
|
|
7678
|
+
options: [
|
|
7679
|
+
...mismatch ? [{ value: "fix", label: `Change the SSO region to ${detected}`, hint: "what the portal page says" }] : [],
|
|
7680
|
+
{ value: "url", label: "Start URL", hint: portal.startUrl || void 0 },
|
|
7681
|
+
{ value: "region", label: "SSO region", hint: portal.region || void 0 },
|
|
7682
|
+
{ value: "done", label: changed ? "Done" : "Back" }
|
|
7683
|
+
]
|
|
7684
|
+
}));
|
|
7685
|
+
if (choice == "done")
|
|
7686
|
+
break;
|
|
7687
|
+
if (choice == "fix")
|
|
7688
|
+
await updatePortal(name, { region: detected }), p4.log.success(`${name} now uses the SSO region ${ui2.accent(detected)}`);
|
|
7689
|
+
else if (choice == "url") {
|
|
7690
|
+
let startUrl = (await ask(p4.text({ message: `Start URL of ${name}`, initialValue: portal.startUrl || "", validate: validateStartUrl }))).trim();
|
|
7691
|
+
if (startUrl == portal.startUrl)
|
|
7692
|
+
continue;
|
|
7693
|
+
await updatePortal(name, { startUrl }), detected = void 0, p4.log.success(`${name} now uses ${ui2.accent(startUrl)}`);
|
|
7694
|
+
} else {
|
|
7695
|
+
let region = await chooseRegion(`SSO region of ${name} ${ui2.dim("(where IAM Identity Center lives)")}`, detected || portal.region);
|
|
7696
|
+
if (region == portal.region)
|
|
7697
|
+
continue;
|
|
7698
|
+
await updatePortal(name, { region }), p4.log.success(`${name} now uses the SSO region ${ui2.accent(region)}`);
|
|
7699
|
+
}
|
|
7700
|
+
changed = !0;
|
|
7701
|
+
}
|
|
7702
|
+
return changed && offerLogin && await ask(p4.confirm({ message: `Log in to ${name} now to check it works?` })) && await ssoLogin({ ssoSession: name }, { label: name, loginKey: name, interactive: !0 }), { changed };
|
|
7703
|
+
}, fixSsoRegion = async (session, loginError) => {
|
|
7704
|
+
let current = session.sso_region, detected = await aws.detectSsoRegion(session.sso_start_url), region = null;
|
|
7705
|
+
if (detected && detected != current ? (p4.log.warn(`${session.name} is set to the SSO region ${current}, but its portal is in ${detected}.`), await ask(p4.confirm({ message: `Change ${session.name} to ${detected} and log in again?` })) && (region = detected)) : p4.log.warn(`AWS rejected the SSO region of ${session.name} (${current}). Pick the region where IAM Identity Center lives (Identity Center console \u203A Settings).`), region || (region = await chooseRegion(`SSO region of ${session.name} ${ui2.dim("(where IAM Identity Center lives)")}`, detected || current)), region == current)
|
|
7706
|
+
throw loginError;
|
|
7707
|
+
return await updatePortal(session.name, { region }), region;
|
|
7708
|
+
};
|
|
7709
|
+
module2.exports = {
|
|
7710
|
+
validateStartUrl,
|
|
7711
|
+
validateRegion,
|
|
7712
|
+
listPortals,
|
|
7713
|
+
findPortal,
|
|
7714
|
+
regionMismatch,
|
|
7715
|
+
portalHint,
|
|
7716
|
+
updatePortal,
|
|
7717
|
+
editPortal,
|
|
7718
|
+
fixSsoRegion
|
|
7719
|
+
};
|
|
7720
|
+
}
|
|
7721
|
+
});
|
|
7722
|
+
|
|
7600
7723
|
// src/home.js
|
|
7601
7724
|
var require_home = __commonJS({
|
|
7602
7725
|
"src/home.js"(exports2, module2) {
|
|
@@ -7661,10 +7784,63 @@ ${gray(S_BAR2)}`;
|
|
|
7661
7784
|
}
|
|
7662
7785
|
});
|
|
7663
7786
|
|
|
7787
|
+
// src/commands/recovery.js
|
|
7788
|
+
var require_recovery = __commonJS({
|
|
7789
|
+
"src/commands/recovery.js"(exports2, module2) {
|
|
7790
|
+
var p4 = (init_dist4(), __toCommonJS(dist_exports2)), aws = require_aws(), transforms = require_transforms(), ui2 = require_ui(), log3 = require_log(), { CancelError: CancelError2 } = require_common(), portals = require_portals(), portalOfError = (err, profiles, sessions) => {
|
|
7791
|
+
let target = err && err.target || {}, name = target.ssoSession || target.profile && ((profiles || []).find((x2) => x2.name == target.profile) || {}).sso_session, session = name && (sessions || []).find((s) => s.name == name);
|
|
7792
|
+
return session ? { name: session.name, startUrl: session.sso_start_url || null, region: session.sso_region || null } : null;
|
|
7793
|
+
}, recoveryOptions = ({ portal, wrongRegion, detectedRegion }) => [
|
|
7794
|
+
portal && wrongRegion && portals.regionMismatch(portal.region, detectedRegion) ? { value: "fix-region", label: `Change the SSO region of ${portal.name} to ${detectedRegion}`, hint: `currently ${portal.region}` } : null,
|
|
7795
|
+
portal ? { value: "edit-portal", label: `Edit the portal ${portal.name}`, hint: "start URL and SSO region" } : null,
|
|
7796
|
+
{ value: "retry", label: "Try again" },
|
|
7797
|
+
{ value: "back", label: "Back to the home screen" }
|
|
7798
|
+
].filter(Boolean), errorLines = (err, { portal, detectedRegion }) => [
|
|
7799
|
+
ui2.strong(err.message || String(err)),
|
|
7800
|
+
err.hint ? `
|
|
7801
|
+
${err.hint}` : null,
|
|
7802
|
+
portal ? `
|
|
7803
|
+
${ui2.dim("Portal ")} ${portal.name} ${ui2.dim(portal.startUrl || "")}` : null,
|
|
7804
|
+
portal ? `${ui2.dim("SSO region")} ${portal.region || "?"}${portals.regionMismatch(portal.region, detectedRegion) ? ` ${ui2.warn(`${ui2.sym.warn} its portal page says ${detectedRegion}`)}` : detectedRegion ? ` ${ui2.dim("(matches its portal page)")}` : ""}` : null,
|
|
7805
|
+
`
|
|
7806
|
+
${ui2.dim(`Details: ${log3.displayPath()}`)}`
|
|
7807
|
+
].filter((v) => v !== null).join(`
|
|
7808
|
+
`), recover = async (err, state, what) => {
|
|
7809
|
+
log3.error(`${what} failed (error screen)`, err);
|
|
7810
|
+
let [, configStr] = await aws.getConfigFile(), portal = portalOfError(err, state.profiles, transforms.listSsoSessions(configStr || "")), detectedRegion = portal && err.wrongRegion && portal.startUrl ? await aws.detectSsoRegion(portal.startUrl) : null;
|
|
7811
|
+
p4.log.error(errorLines(err, { portal, detectedRegion }));
|
|
7812
|
+
let choice = await p4.select({ message: "What now?", options: recoveryOptions({ portal, wrongRegion: err.wrongRegion, detectedRegion }) });
|
|
7813
|
+
if (p4.isCancel(choice) || choice == "back")
|
|
7814
|
+
return "back";
|
|
7815
|
+
try {
|
|
7816
|
+
if (choice == "fix-region")
|
|
7817
|
+
await portals.updatePortal(portal.name, { region: detectedRegion }), p4.log.success(`${portal.name} now uses the SSO region ${ui2.accent(detectedRegion)}. Trying again\u2026`);
|
|
7818
|
+
else if (choice == "edit-portal") {
|
|
7819
|
+
let { changed } = await portals.editPortal(portal.name, state, { offerLogin: !1, detectedRegion: err.wrongRegion ? detectedRegion : void 0 });
|
|
7820
|
+
if (!changed)
|
|
7821
|
+
return "back";
|
|
7822
|
+
}
|
|
7823
|
+
} catch (e) {
|
|
7824
|
+
if (e instanceof CancelError2)
|
|
7825
|
+
return "back";
|
|
7826
|
+
if (!(e instanceof ui2.CliError))
|
|
7827
|
+
throw e;
|
|
7828
|
+
return log3.error("fixing the portal failed", e), ui2.printError(e), "back";
|
|
7829
|
+
}
|
|
7830
|
+
return "retry";
|
|
7831
|
+
};
|
|
7832
|
+
module2.exports = {
|
|
7833
|
+
portalOfError,
|
|
7834
|
+
recoveryOptions,
|
|
7835
|
+
recover
|
|
7836
|
+
};
|
|
7837
|
+
}
|
|
7838
|
+
});
|
|
7839
|
+
|
|
7664
7840
|
// src/commands/switch.js
|
|
7665
7841
|
var require_switch = __commonJS({
|
|
7666
7842
|
"src/commands/switch.js"(exports2, module2) {
|
|
7667
|
-
var p4 = (init_dist4(), __toCommonJS(dist_exports2)), aws = require_aws(), settings2 = require_settings(), shell = require_shell(), rank = require_rank(), ui2 = require_ui(), log3 = require_log(), { ask, preflight, loadState, loginKey, describe, loginState, CancelError: CancelError2 } = require_common(), { ssoLogin } = require_login_flow(), { home, ACTION_PREFIX } = require_home(), header = async (state, { warnings } = {}) => {
|
|
7843
|
+
var p4 = (init_dist4(), __toCommonJS(dist_exports2)), aws = require_aws(), settings2 = require_settings(), shell = require_shell(), rank = require_rank(), ui2 = require_ui(), log3 = require_log(), { ask, preflight, loadState, loginKey, describe, loginState, CancelError: CancelError2 } = require_common(), { ssoLogin } = require_login_flow(), { home, ACTION_PREFIX } = require_home(), { recover } = require_recovery(), header = async (state, { warnings } = {}) => {
|
|
7668
7844
|
p4.intro(`${ui2.badge("switch-profile")} ${ui2.dim(settings2.CLI_VERSION)}`);
|
|
7669
7845
|
let lines = [];
|
|
7670
7846
|
if (state.defaultProfile) {
|
|
@@ -7690,6 +7866,7 @@ ${ui2.dim(viaShortcut ? "Switching clears them from this terminal." : `Unset ${s
|
|
|
7690
7866
|
}, ACTIONS = [
|
|
7691
7867
|
{ key: "login", label: "Log in" },
|
|
7692
7868
|
{ key: "add", label: "Add" },
|
|
7869
|
+
{ key: "portals", label: "Portals" },
|
|
7693
7870
|
{ key: "rename", label: "Rename" },
|
|
7694
7871
|
{ key: "remove", label: "Remove" },
|
|
7695
7872
|
{ key: "logout", label: "Log out" },
|
|
@@ -7787,35 +7964,51 @@ ${ui2.dim("Tip: enable per-terminal switching in Settings.")}` : ""}`);
|
|
|
7787
7964
|
${ui2.dim(`Open a new terminal or run: ${installed.reload}`)}`), installed.name == "powershell" && p4.log.message(ui2.dim("If PowerShell refuses to load your profile: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned"));
|
|
7788
7965
|
}, runAction = async (key, state, opts) => {
|
|
7789
7966
|
let commands2 = require_commands();
|
|
7790
|
-
return key == "login" ? commands2.login(state.defaultName, { ...opts, fromPicker: !0 }) : key == "rename" ? commands2.rename(null, null, { ...opts, fromPicker: !0 }) : commands2[key](opts.args || [], { ...opts, fromPicker: !0 });
|
|
7967
|
+
return key == "login" ? commands2.login(state.defaultName, { ...opts, fromPicker: !0 }) : key == "rename" ? commands2.rename(null, null, { ...opts, fromPicker: !0 }) : key == "portals" ? commands2.portals(null, { ...opts, fromPicker: !0 }) : commands2[key](opts.args || [], { ...opts, fromPicker: !0 });
|
|
7791
7968
|
}, routeChoice = (choice) => {
|
|
7792
7969
|
if (p4.isCancel(choice))
|
|
7793
7970
|
return { type: "quit" };
|
|
7794
7971
|
let value = String(choice);
|
|
7795
7972
|
return value.startsWith(ACTION_PREFIX) ? { type: "page", key: value.slice(ACTION_PREFIX.length) } : { type: "switch", name: value };
|
|
7796
|
-
}, afterPage = (result, err) => err ? err instanceof CancelError2 ? "back" : err instanceof ui2.CliError ? "error" : "throw" : result && result.switched ? "exit" : "home",
|
|
7797
|
-
let
|
|
7973
|
+
}, afterPage = (result, err) => err ? err instanceof CancelError2 ? "back" : err instanceof ui2.CliError ? "error" : "throw" : result && result.switched ? "exit" : "home", findOrFail = (state, name) => {
|
|
7974
|
+
let profile = state.profiles.find((x2) => x2.name == name);
|
|
7975
|
+
if (!profile)
|
|
7976
|
+
throw new ui2.CliError(`There is no profile named ${name} any more.`, { code: 3 });
|
|
7977
|
+
return profile;
|
|
7978
|
+
}, withRecovery = async (run2, state, what, { exitOnCancel } = {}) => {
|
|
7798
7979
|
for (; ; ) {
|
|
7799
|
-
await header(state, { warnings: first });
|
|
7800
|
-
let route = routeChoice(await pick(state, first ? initialQuery : void 0));
|
|
7801
|
-
if (first = !1, route.type == "quit") {
|
|
7802
|
-
p4.outro(ui2.dim(`Bye ${ui2.unicode ? "\u{1F44B}" : ""}`));
|
|
7803
|
-
return;
|
|
7804
|
-
}
|
|
7805
|
-
if (route.type == "switch")
|
|
7806
|
-
return switchTo(state.profiles.find((x2) => x2.name == route.name), state, opts);
|
|
7807
7980
|
let result, error;
|
|
7808
7981
|
try {
|
|
7809
|
-
result = await
|
|
7982
|
+
result = await run2(state);
|
|
7810
7983
|
} catch (err) {
|
|
7811
7984
|
error = err;
|
|
7812
7985
|
}
|
|
7813
7986
|
let next = afterPage(result, error);
|
|
7814
|
-
if (next == "exit")
|
|
7987
|
+
if (next == "exit" || next == "home")
|
|
7815
7988
|
return result;
|
|
7816
|
-
if (next == "throw")
|
|
7989
|
+
if (next == "throw" || next == "back" && exitOnCancel)
|
|
7817
7990
|
throw error;
|
|
7818
|
-
next == "back"
|
|
7991
|
+
if (next == "back") {
|
|
7992
|
+
p4.cancel(ui2.dim(`${ui2.unicode ? "\u2190" : "<-"} Back`));
|
|
7993
|
+
return;
|
|
7994
|
+
}
|
|
7995
|
+
if (state = await loadState(), await recover(error, state, what) != "retry")
|
|
7996
|
+
return;
|
|
7997
|
+
state = await loadState();
|
|
7998
|
+
}
|
|
7999
|
+
}, homeLoop = async (state, opts, initialQuery) => {
|
|
8000
|
+
let first = !0;
|
|
8001
|
+
for (; ; ) {
|
|
8002
|
+
await header(state, { warnings: first });
|
|
8003
|
+
let route = routeChoice(await pick(state, first ? initialQuery : void 0));
|
|
8004
|
+
if (first = !1, route.type == "quit") {
|
|
8005
|
+
p4.outro(ui2.dim(`Bye ${ui2.unicode ? "\u{1F44B}" : ""}`));
|
|
8006
|
+
return;
|
|
8007
|
+
}
|
|
8008
|
+
let result = route.type == "switch" ? await withRecovery((s) => switchTo(findOrFail(s, route.name), s, opts), state, `switch to ${route.name}`, { exitOnCancel: !0 }) : await withRecovery((s) => runAction(route.key, s, opts), state, route.key);
|
|
8009
|
+
if (afterPage(result) == "exit")
|
|
8010
|
+
return result;
|
|
8011
|
+
state = await loadState();
|
|
7819
8012
|
}
|
|
7820
8013
|
}, switchCommand = async (query, opts) => {
|
|
7821
8014
|
let interactive = ui2.isInteractive(opts);
|
|
@@ -7828,8 +8021,13 @@ ${ui2.dim(`Open a new terminal or run: ${installed.reload}`)}`), installed.name
|
|
|
7828
8021
|
}
|
|
7829
8022
|
if (query) {
|
|
7830
8023
|
let { profile, candidates } = rank.resolveQuery(state.profiles, query, { usage: state.settings.usage, current: state.defaultName });
|
|
7831
|
-
if (profile)
|
|
7832
|
-
|
|
8024
|
+
if (profile) {
|
|
8025
|
+
if (!interactive || opts.json)
|
|
8026
|
+
return switchTo(profile, state, { ...opts, interactive });
|
|
8027
|
+
p4.intro(`${ui2.badge("switch-profile")} ${ui2.dim(settings2.CLI_VERSION)}`);
|
|
8028
|
+
let result = await withRecovery((s) => switchTo(findOrFail(s, profile.name), s, { ...opts, interactive }), state, `switch to ${profile.name}`, { exitOnCancel: !0 });
|
|
8029
|
+
return result || homeLoop(await loadState(), { ...opts, interactive });
|
|
8030
|
+
}
|
|
7833
8031
|
if (!interactive)
|
|
7834
8032
|
throw new ui2.CliError(candidates.length ? `"${query}" matches ${candidates.length} profiles.` : `No profile matches "${query}".`, {
|
|
7835
8033
|
code: 3,
|
|
@@ -7854,7 +8052,7 @@ ${ui2.dim(`Open a new terminal or run: ${installed.reload}`)}`), installed.name
|
|
|
7854
8052
|
// src/commands/index.js
|
|
7855
8053
|
var require_commands = __commonJS({
|
|
7856
8054
|
"src/commands/index.js"(exports2, module2) {
|
|
7857
|
-
var p4 = (init_dist4(), __toCommonJS(dist_exports2)), log3 = require_log(), aws = require_aws(), transforms = require_transforms(), settings2 = require_settings(), shell = require_shell(), migrate = require_migrate(), rank = require_rank(), { resolveLoginMode, isRemoteSession, configureSsoFailure } = require_login(), ui2 = require_ui(), { ask, preflight, loadState, loginKey, describe, loginState, upgradeLegacySso } = require_common(), { ssoLogin, rememberLogin } = require_login_flow(), { switchCommand, switchTo, connect, enableShortcut, exportHint } = require_switch(), intro2 = (opts) => {
|
|
8055
|
+
var p4 = (init_dist4(), __toCommonJS(dist_exports2)), log3 = require_log(), aws = require_aws(), transforms = require_transforms(), settings2 = require_settings(), shell = require_shell(), migrate = require_migrate(), rank = require_rank(), { resolveLoginMode, isRemoteSession, configureSsoFailure } = require_login(), ui2 = require_ui(), { ask, preflight, loadState, loginKey, describe, loginState, upgradeLegacySso, chooseRegion } = require_common(), { ssoLogin, rememberLogin } = require_login_flow(), portals = require_portals(), { switchCommand, switchTo, connect, enableShortcut, exportHint } = require_switch(), intro2 = (opts) => {
|
|
7858
8056
|
!opts.fromPicker && !opts.json && p4.intro(`${ui2.badge("switch-profile")} ${ui2.dim(settings2.CLI_VERSION)}`);
|
|
7859
8057
|
}, begin = async (opts, { needsInput } = {}) => {
|
|
7860
8058
|
let interactive = ui2.isInteractive(opts);
|
|
@@ -7909,12 +8107,7 @@ var require_commands = __commonJS({
|
|
|
7909
8107
|
return p4.outro(ui2.dim("Nothing changed."));
|
|
7910
8108
|
}
|
|
7911
8109
|
await aws.logoutAll(), await settings2.update({ logins: {} }), p4.outro(`${ui2.ok(ui2.sym.ok)} Logged out ${ui2.unicode ? "\u{1F44B}" : ""}${isRemoteSession(process.env, process.platform) ? ui2.dim(" This machine no longer has AWS access.") : ""}`);
|
|
7912
|
-
},
|
|
7913
|
-
message,
|
|
7914
|
-
maxItems: 8,
|
|
7915
|
-
initialValue,
|
|
7916
|
-
options: aws.regions.map((r2) => ({ value: r2.code, label: r2.code, hint: r2.name }))
|
|
7917
|
-
})), validateProfileName = (taken) => (value) => {
|
|
8110
|
+
}, validateProfileName = (taken) => (value) => {
|
|
7918
8111
|
if (!value)
|
|
7919
8112
|
return "A name is required.";
|
|
7920
8113
|
if (!/^[a-z0-9][a-z0-9_-]+$/.test(value))
|
|
@@ -8004,7 +8197,7 @@ AWS recommends SSO or console sign-in instead.`), !await ask(p4.confirm({ messag
|
|
|
8004
8197
|
]
|
|
8005
8198
|
})) : "__new__";
|
|
8006
8199
|
if (choice == "__new__") {
|
|
8007
|
-
let startUrl = await ask(p4.text({ message: "SSO start URL", placeholder: "https://acme.awsapps.com/start", validate:
|
|
8200
|
+
let startUrl = (await ask(p4.text({ message: "SSO start URL", placeholder: "https://acme.awsapps.com/start", validate: portals.validateStartUrl }))).trim(), detected = await aws.detectSsoRegion(startUrl), region2 = await chooseRegion(`SSO region ${ui2.dim(detected ? `(where IAM Identity Center lives: ${detected}, read from the portal)` : "(where IAM Identity Center lives)")}`, detected || "us-east-1"), suggested = transforms.sessionNameFromUrl(startUrl).replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
8008
8201
|
sessionName = await ask(p4.text({ message: `Short name for this portal ${ui2.dim("(usually the client)")}`, initialValue: suggested, validate: (v) => /^[a-zA-Z0-9_-]+$/.test(v || "") ? sessions.some((s) => s.name == v) ? `${v} already exists.` : void 0 : "Letters, numbers, - and _ only." })), configStr = transforms.addSsoSession(configStr, { name: sessionName, startUrl, region: region2, version: settings2.CLI_VERSION }), await aws.writeAwsFile(aws.AWS_CONFIG_FILE, configStr);
|
|
8009
8202
|
} else
|
|
8010
8203
|
sessionName = choice;
|
|
@@ -8013,7 +8206,17 @@ AWS recommends SSO or console sign-in instead.`), !await ask(p4.confirm({ messag
|
|
|
8013
8206
|
if (!token) {
|
|
8014
8207
|
if (!interactive)
|
|
8015
8208
|
throw new ui2.CliError(`Not logged in to ${sessionName}.`, { code: 2, hint: `Run: aws sso login --sso-session ${sessionName}` });
|
|
8016
|
-
|
|
8209
|
+
for (; ; )
|
|
8210
|
+
try {
|
|
8211
|
+
await ssoLogin({ ssoSession: sessionName }, { label: sessionName, loginKey: sessionName, interactive });
|
|
8212
|
+
break;
|
|
8213
|
+
} catch (err) {
|
|
8214
|
+
if (!err.wrongRegion)
|
|
8215
|
+
throw err;
|
|
8216
|
+
let region2 = await portals.fixSsoRegion(session, err);
|
|
8217
|
+
configStr = (await aws.getConfigFile())[1] || configStr, session = { ...session, sso_region: region2 };
|
|
8218
|
+
}
|
|
8219
|
+
if (token = await aws.readSsoToken({ ssoSession: sessionName }), !token)
|
|
8017
8220
|
throw new ui2.CliError(`Logged in, but no token was found for ${sessionName}.`);
|
|
8018
8221
|
}
|
|
8019
8222
|
let spin = opts.json ? null : ui2.spinner();
|
|
@@ -8137,6 +8340,43 @@ AWS recommends SSO or console sign-in instead.`), !await ask(p4.confirm({ messag
|
|
|
8137
8340
|
p4.log.success([`Renamed ${ui2.accent(from)} to ${ui2.accent(to)}`, ...lines].join(`
|
|
8138
8341
|
`)), result.processes.length && p4.log.warn(`The credential_process of ${result.processes.join(", ")} still uses --profile ${from}. Update ${result.processes.length == 1 ? "it" : "them"} in ~/.aws/config.`), inTerminal && !terminal && p4.log.warn(`This terminal still uses ${from}. Run:
|
|
8139
8342
|
${ui2.accent(exportHint(to))}`), p4.outro(ui2.dim(`Other terminals, scripts and tools that use ${from} (AWS_PROFILE or --profile) need the new name.`));
|
|
8343
|
+
}, portalsCommand = async (name, opts) => {
|
|
8344
|
+
let interactive = await begin(opts), state = await loadState();
|
|
8345
|
+
if (opts.region || opts.startUrl) {
|
|
8346
|
+
if (!name)
|
|
8347
|
+
throw new ui2.CliError("Which portal?", { code: 3, hint: "Run: switch-profile portals <name> --region <region>" });
|
|
8348
|
+
let invalid = opts.region && portals.validateRegion(opts.region) || opts.startUrl && portals.validateStartUrl(opts.startUrl);
|
|
8349
|
+
if (invalid)
|
|
8350
|
+
throw new ui2.CliError(invalid, { code: 3 });
|
|
8351
|
+
await portals.findPortal(state, name), await portals.updatePortal(name, { region: opts.region, startUrl: opts.startUrl });
|
|
8352
|
+
let portal = await portals.findPortal(state, name);
|
|
8353
|
+
return opts.json ? void process.stdout.write(JSON.stringify(portal) + `
|
|
8354
|
+
`) : (p4.log.success(`Saved ${ui2.accent(name)}: ${ui2.join([portal.startUrl, portal.region])}`), p4.outro(ui2.dim(`Log in again: switch-profile login <a ${name} profile>`)));
|
|
8355
|
+
}
|
|
8356
|
+
if (!interactive || opts.json) {
|
|
8357
|
+
let list = await portals.listPortals(state, { detect: !!opts.check }), selected = name ? list.filter((x2) => x2.name == name) : list;
|
|
8358
|
+
if (name && !selected.length && await portals.findPortal(state, name), opts.json)
|
|
8359
|
+
return void process.stdout.write(JSON.stringify({ portals: selected }) + `
|
|
8360
|
+
`);
|
|
8361
|
+
for (let x2 of selected)
|
|
8362
|
+
console.log(`${x2.name} ${x2.region || "-"} ${x2.startUrl || "-"} ${x2.profiles.length} profiles${opts.check ? ` portal says ${x2.detectedRegion || "?"}` : ""}`);
|
|
8363
|
+
return;
|
|
8364
|
+
}
|
|
8365
|
+
if (!name) {
|
|
8366
|
+
let spin = ui2.spinner();
|
|
8367
|
+
spin.start("Reading the portals");
|
|
8368
|
+
let list = await portals.listPortals(state, { detect: !0 });
|
|
8369
|
+
if (spin.stop(`${list.length} SSO portal${list.length == 1 ? "" : "s"}`), !list.length)
|
|
8370
|
+
return p4.outro(ui2.dim("No SSO portals yet. Add one with Add \u203A Accounts from an SSO portal."));
|
|
8371
|
+
let picked = await ask(p4.select({
|
|
8372
|
+
message: "Which portal?",
|
|
8373
|
+
options: list.map((x2) => ({ value: x2.name, label: x2.name, hint: portals.portalHint(x2) }))
|
|
8374
|
+
})), { changed: changed2 } = await portals.editPortal(picked, state, { detectedRegion: list.find((x2) => x2.name == picked).detectedRegion });
|
|
8375
|
+
return p4.outro(ui2.dim(changed2 ? "Saved." : "Nothing changed."));
|
|
8376
|
+
}
|
|
8377
|
+
await portals.findPortal(state, name);
|
|
8378
|
+
let { changed } = await portals.editPortal(name, state);
|
|
8379
|
+
p4.outro(ui2.dim(changed ? "Saved." : "Nothing changed."));
|
|
8140
8380
|
}, LOGIN_MODE_LABELS = {
|
|
8141
8381
|
auto: "Auto: device code over SSH, browser otherwise",
|
|
8142
8382
|
device: "Always device code (approve from any device)",
|
|
@@ -8189,6 +8429,7 @@ AWS recommends SSO or console sign-in instead.`), !await ask(p4.confirm({ messag
|
|
|
8189
8429
|
add,
|
|
8190
8430
|
remove,
|
|
8191
8431
|
rename,
|
|
8432
|
+
portals: portalsCommand,
|
|
8192
8433
|
settings: settingsCommand
|
|
8193
8434
|
};
|
|
8194
8435
|
}
|
|
@@ -8205,6 +8446,7 @@ Examples:
|
|
|
8205
8446
|
$ sp login log in again now (device code over SSH)
|
|
8206
8447
|
$ sp add import every account of an SSO portal
|
|
8207
8448
|
$ sp rename <profile> <new> give a profile a friendlier name
|
|
8449
|
+
$ sp portals check or fix an SSO portal's start URL and region
|
|
8208
8450
|
$ switch-profile status --json
|
|
8209
8451
|
|
|
8210
8452
|
'sp' is the shortcut switch-profile installs on first use (or from Settings). It also
|
|
@@ -8234,6 +8476,6 @@ Exit codes: 0 ok \xB7 1 error \xB7 2 login required \xB7 3 bad input \xB7 130 ca
|
|
|
8234
8476
|
}, main = (argv) => {
|
|
8235
8477
|
log2.start({ version: CLI_VERSION, argv }), process.on("uncaughtException", crashed), process.on("unhandledRejection", crashed), process.on("exit", (code) => log2.info(`exit ${code}`));
|
|
8236
8478
|
let program = new Command();
|
|
8237
|
-
return program.name("switch-profile").description("Switch between AWS profiles, log in with SSO, and keep every terminal on the right account.").version(CLI_VERSION, "-v, --version").argument("[profile...]", "profile name or search text").option("--json", "machine-readable output").option("--no-input", "never prompt (fails with a hint instead)").option("--debug", "show error details").addHelpText("after", EXAMPLES).showSuggestionAfterError().action(run((words, opts) => commands.switch((words || []).join(" "), opts))), program.command("use").argument("<profile>").description("switch to a profile by its exact name").action(run(commands.use)), program.command("status").description("show the current profile and login state").action(run((opts) => commands.status([], opts))), program.command("login").argument("[profile]", "defaults to this terminal's profile, then the default profile").description("log in again now, even if the session is still valid").option("--device", "use a device code (approve from any device)").option("--browser", "use the browser on this machine").action(run(commands.login)), program.command("logout").description("end every SSO session on this machine").option("-y, --yes", "do not ask for confirmation").action(run((opts) => commands.logout([], opts))), program.command("add").description("add profiles: import an SSO portal, or create one").option("--from-sso [session]", "import (or re-sync) every account and role of an [sso-session]").option("--region <region>", "default region of imported profiles").option("--prefix <prefix>", "name prefix of imported profiles").option("--prune", "remove imported profiles that no longer exist").option("-y, --yes", "do not ask for confirmation").action(run((opts) => commands.add([], opts))), program.command("remove").alias("rm").argument("[profiles...]").description("remove profiles").option("-y, --yes", "do not ask for confirmation").action(run(commands.remove)), program.command("rename").alias("mv").argument("[profile]").argument("[new-name]").description("rename a profile").action(run(commands.rename)), program.command("settings").description("per-terminal switching, login mode and more").action(run((opts) => commands.settings([], opts))), program.command("switch", { hidden: !0 }).action(run((opts) => commands.switch("", opts))), program.parseAsync(argv);
|
|
8479
|
+
return program.name("switch-profile").description("Switch between AWS profiles, log in with SSO, and keep every terminal on the right account.").version(CLI_VERSION, "-v, --version").argument("[profile...]", "profile name or search text").option("--json", "machine-readable output").option("--no-input", "never prompt (fails with a hint instead)").option("--debug", "show error details").addHelpText("after", EXAMPLES).showSuggestionAfterError().action(run((words, opts) => commands.switch((words || []).join(" "), opts))), program.command("use").argument("<profile>").description("switch to a profile by its exact name").action(run(commands.use)), program.command("status").description("show the current profile and login state").action(run((opts) => commands.status([], opts))), program.command("login").argument("[profile]", "defaults to this terminal's profile, then the default profile").description("log in again now, even if the session is still valid").option("--device", "use a device code (approve from any device)").option("--browser", "use the browser on this machine").action(run(commands.login)), program.command("logout").description("end every SSO session on this machine").option("-y, --yes", "do not ask for confirmation").action(run((opts) => commands.logout([], opts))), program.command("add").description("add profiles: import an SSO portal, or create one").option("--from-sso [session]", "import (or re-sync) every account and role of an [sso-session]").option("--region <region>", "default region of imported profiles").option("--prefix <prefix>", "name prefix of imported profiles").option("--prune", "remove imported profiles that no longer exist").option("-y, --yes", "do not ask for confirmation").action(run((opts) => commands.add([], opts))), program.command("remove").alias("rm").argument("[profiles...]").description("remove profiles").option("-y, --yes", "do not ask for confirmation").action(run(commands.remove)), program.command("rename").alias("mv").argument("[profile]").argument("[new-name]").description("rename a profile").action(run(commands.rename)), program.command("portals").alias("portal").argument("[name]", "the [sso-session] to edit").description("list SSO portals, check their region, and edit the start URL or region").option("--region <region>", "set the SSO region (where IAM Identity Center lives)").option("--start-url <url>", "set the start URL").option("--check", "with --json or no terminal: also read each portal's region from its start page").action(run(commands.portals)), program.command("settings").description("per-terminal switching, login mode and more").action(run((opts) => commands.settings([], opts))), program.command("switch", { hidden: !0 }).action(run((opts) => commands.switch("", opts))), program.parseAsync(argv);
|
|
8238
8480
|
};
|
|
8239
8481
|
module.exports = { main };
|