switch-profile 0.1.2 → 0.2.2

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.
@@ -0,0 +1,323 @@
1
+ # AWS Profile Management
2
+
3
+ This document explains how `switch-profile` manages AWS profiles, SSO sessions, and credentials internally.
4
+
5
+ ## Overview
6
+
7
+ All profile and credential logic lives in `src/aws/index.js`. This module directly reads and writes AWS configuration files using regex-based INI parsing - it does not shell out to `aws configure` for most operations.
8
+
9
+ ## File Locations
10
+
11
+ | Path | Purpose | Managed By |
12
+ |------|---------|------------|
13
+ | `~/.aws/config` | Profile settings (region, output, SSO metadata) | `switch-profile` + `aws configure sso` |
14
+ | `~/.aws/credentials` | Access keys, secrets, session tokens | `switch-profile` |
15
+ | `~/.aws/sso/cache/` | SSO session tokens (JSON files) | AWS CLI |
16
+ | `~/.aws/cli/cache/` | Temporary SSO credentials (JSON files) | AWS CLI |
17
+
18
+ ## Constants
19
+
20
+ ```javascript
21
+ SSO_GET_CREDS_TIMEOUT = 300000 // 5 minutes to complete SSO login
22
+ ```
23
+
24
+ ## Functions Reference
25
+
26
+ ### Profile Listing
27
+
28
+ #### `listProfiles()`
29
+
30
+ Reads `~/.aws/config` and extracts all profile sections except `[default]`.
31
+
32
+ **Returns:** `[errors, profiles[]]`
33
+
34
+ Each profile object contains:
35
+
36
+ ```javascript
37
+ {
38
+ name: 'sso-dev', // Profile name
39
+ friendlyName: 'sso-dev (SSO [role:Admin - account:123456])', // Display name
40
+ region: 'us-east-1', // AWS region
41
+ output: 'json', // Output format
42
+ sso_start_url: 'https://my.awsapps.com/start', // null if not SSO
43
+ sso_region: 'us-east-1', // null if not SSO
44
+ sso_account_id: '123456789012', // null if not SSO
45
+ sso_role_name: 'Admin' // null if not SSO
46
+ }
47
+ ```
48
+
49
+ **Parsing logic:**
50
+ 1. Matches all `[...]` sections in the config file.
51
+ 2. For each section, extracts the content between it and the next `[` bracket.
52
+ 3. Splits content by newline and extracts key-value pairs using `getParam()`.
53
+ 4. Strips `[profile ...]` prefix to get the profile name.
54
+ 5. For SSO profiles, uses `sso_region` as the effective region.
55
+ 6. Builds a `friendlyName` that includes role and account info for SSO profiles.
56
+
57
+ #### `getDefaultProfile()`
58
+
59
+ Reads the `[default]` section from `~/.aws/credentials`.
60
+
61
+ **Returns:** `[errors, profile]`
62
+
63
+ ```javascript
64
+ {
65
+ aws_access_key_id: 'AKIA...',
66
+ aws_secret_access_key: '...',
67
+ aws_session_token: '...', // null for standard profiles
68
+ expiry_date: '2021-07-17...', // null for standard profiles
69
+ profile: 'sso-dev' // Which profile these creds belong to
70
+ }
71
+ ```
72
+
73
+ The `profile` and `expiry_date` fields are custom additions by `switch-profile` - they are not standard AWS CLI fields. They are written into the credentials file so the tool can track which profile is currently active and when it expires.
74
+
75
+ ### Credential Retrieval
76
+
77
+ #### `getCredentials(profile, ssoUrl)`
78
+
79
+ The main credential retrieval function. Behaves differently for SSO vs. standard profiles.
80
+
81
+ **For SSO profiles** (when `ssoUrl` is provided):
82
+
83
+ 1. Calls `refreshSsoSession()` to ensure a valid SSO session exists.
84
+ 2. Calls `getSsoCredentials()` to get temporary AWS credentials via the CLI cache.
85
+ 3. If credentials fail with "session associated with this profile has expired", forces a session refresh and retries.
86
+ 4. If `getSsoCredentials()` returns `null` (cache-based lookup failed), falls back to `aws configure export-credentials --profile <name> --format process` which directly outputs credentials as JSON.
87
+ 5. If both approaches fail, throws an error.
88
+
89
+ **For standard profiles** (when `ssoUrl` is null):
90
+
91
+ 1. Reads `~/.aws/credentials`.
92
+ 2. Finds the `[profile-name]` section.
93
+ 3. Extracts `aws_access_key_id`, `aws_secret_access_key`, and `aws_session_token`.
94
+
95
+ **Returns:** `[errors, { aws_access_key_id, aws_secret_access_key, aws_session_token, expiry_date }]`
96
+
97
+ #### `getSsoCredentials(profile)`
98
+
99
+ Gets SSO credentials by running `aws configure list --profile <profile>`, which triggers the AWS CLI to populate its credential cache.
100
+
101
+ **Process:**
102
+ 1. Executes `aws configure list --profile <profile>`.
103
+ 2. Parses the output to extract the last 4 characters of the access key and secret key (the CLI masks them as `****XXXX`).
104
+ 3. Calls `getSsoCredsFromCacheFile()` to find the full credentials in the CLI cache.
105
+
106
+ **Returns:** `[errors, creds]` or `[errors, null]` if credentials not found.
107
+
108
+ #### `getSsoCredsFromCacheFile(access_key_end, secret_key_end)`
109
+
110
+ Searches `~/.aws/cli/cache/` for a JSON file containing credentials that match the given key suffixes.
111
+
112
+ **Matching criteria:**
113
+ - `ProviderType` must be `"sso"`.
114
+ - `Credentials.Expiration` must be at least 2 minutes in the future.
115
+ - Last 4 characters of `AccessKeyId` must match `access_key_end`.
116
+ - Last 4 characters of `SecretAccessKey` must match `secret_key_end`.
117
+
118
+ **Returns:** `[errors, creds]` or `[errors, null]` if no match found.
119
+
120
+ ### SSO Session Management
121
+
122
+ #### `getSsoSession(ssoUrl)`
123
+
124
+ Finds a valid SSO session token in `~/.aws/sso/cache/`.
125
+
126
+ **Process:**
127
+ 1. Extracts the host from `ssoUrl` (e.g., `my.awsapps.com`).
128
+ 2. Lists all `*.json` files in `~/.aws/sso/cache/`.
129
+ 3. For each file, checks if:
130
+ - `startUrl` host matches the SSO URL host.
131
+ - `expiresAt` is at least 2 minutes in the future.
132
+ - `accessToken` is present.
133
+
134
+ **Returns:** `[errors, session]` or `[errors, null]` if no valid session found.
135
+
136
+ #### `isSSOexpired(ssoSession)`
137
+
138
+ Checks if an SSO session will expire within the next 2 minutes.
139
+
140
+ **Returns:** `boolean`
141
+
142
+ #### `refreshSsoSession(profile, ssoUrl, options)`
143
+
144
+ Ensures a valid SSO session exists. If not, triggers a new SSO login.
145
+
146
+ **Process:**
147
+ 1. Check for existing valid session via `getSsoSession()`.
148
+ 2. If expired or `options.force` is true:
149
+ - Runs `aws sso login --profile <profile>` (opens browser).
150
+ - Polls `getSsoSession()` every 2 seconds.
151
+ - Waits up to 5 minutes (`SSO_GET_CREDS_TIMEOUT`) for the session to appear.
152
+ 3. Returns the session object, or throws a timeout error.
153
+
154
+ ### Profile Modification
155
+
156
+ #### `updateDefaultProfile({ profile, region, expiry_date, aws_access_key_id, aws_secret_access_key, aws_session_token })`
157
+
158
+ Updates the `[default]` section in both `~/.aws/credentials` and `~/.aws/config`.
159
+
160
+ **In `~/.aws/credentials`:**
161
+ ```ini
162
+ [default]
163
+ aws_access_key_id = AKIA...
164
+ aws_secret_access_key = ...
165
+ aws_session_token = ... # Only for SSO profiles
166
+ expiry_date = 2021-07-17T... # Only for SSO profiles
167
+ profile = sso-dev # Custom field: tracks active profile
168
+ ```
169
+
170
+ **In `~/.aws/config`:**
171
+ ```ini
172
+ [default]
173
+ region = us-east-1
174
+ output = json
175
+ ```
176
+
177
+ **Approach:** Uses regex to find the existing `[default]` section and replaces it. If no `[default]` section exists, prepends it. All other profile sections are preserved unchanged.
178
+
179
+ #### `createProfile({ name, aws_access_key_id, aws_secret_access_key, region })`
180
+
181
+ Creates a standard (non-SSO) profile.
182
+
183
+ **Writes to `~/.aws/config`:**
184
+ ```ini
185
+ [profile my-profile]
186
+ region = us-east-1
187
+ output = json
188
+ ```
189
+
190
+ **Writes to `~/.aws/credentials`:**
191
+ ```ini
192
+ [my-profile]
193
+ aws_access_key_id = AKIA...
194
+ aws_secret_access_key = ...
195
+ ```
196
+
197
+ If the config or credentials files don't exist, they are created with a default `[default]` section first.
198
+
199
+ #### `createSsoProfile(name)`
200
+
201
+ Creates an SSO profile by spawning the interactive `aws configure sso --profile <name>` command. This hands control to the AWS CLI, which walks the user through the SSO setup (URL, region, account, role).
202
+
203
+ Uses `spawn` with `stdio: 'inherit'` so the user interacts directly with the AWS CLI process. Checks the exit code of the spawned process and throws an error if it is non-zero, indicating the SSO setup failed.
204
+
205
+ #### `deleteProfiles(profiles)`
206
+
207
+ Deletes one or more profiles from both `~/.aws/config` and `~/.aws/credentials`.
208
+
209
+ **Rules:**
210
+ - The `default` profile cannot be deleted.
211
+ - Uses regex matching to find and remove the profile section and everything up to the next `[` bracket.
212
+
213
+ #### `deleteProfileFromConfig(profile, fileContent)` / `deleteProfileFromCreds(profile, fileContent)`
214
+
215
+ Internal helpers that remove a single profile section from file content using regex. The config version handles both `[profile name]` and `[name]` syntax. The creds version handles `[name]` syntax only.
216
+
217
+ ### AWS CLI Detection
218
+
219
+ #### `awsCliV2Exists(noFailIfMissing)`
220
+
221
+ Checks that AWS CLI v2+ is installed.
222
+
223
+ **Process:**
224
+ 1. Runs `which aws` (or `where aws` on Windows) to check if the command exists.
225
+ 2. Runs `aws --version` and parses the major version number.
226
+ 3. Rejects if version is 1 or lower.
227
+
228
+ **Parameters:**
229
+ - `noFailIfMissing`: If `true`, returns `false` instead of throwing when AWS CLI is not found.
230
+
231
+ ### Utility
232
+
233
+ #### `getParam(params, paramName)`
234
+
235
+ Extracts a parameter value from an array of lines in `key = value` format.
236
+
237
+ ```javascript
238
+ getParam([' region = us-east-1', ' output = json'], 'region')
239
+ // Returns: 'us-east-1'
240
+ ```
241
+
242
+ #### `getCredsFile()` / `getConfigFile()`
243
+
244
+ Reads the credentials or config file and returns its contents as a string. Returns an empty string if the file doesn't exist.
245
+
246
+ ## SSO Credential Flow Diagram
247
+
248
+ ```
249
+ User selects an SSO profile
250
+ │
251
+ ▼
252
+ getCredentials(profile, ssoUrl)
253
+ │
254
+ ▼
255
+ refreshSsoSession(profile, ssoUrl)
256
+ │
257
+ ├── getSsoSession(ssoUrl)
258
+ │ │
259
+ │ ▼
260
+ │ Search ~/.aws/sso/cache/*.json
261
+ │ for matching, non-expired session
262
+ │ │
263
+ │ ├── Found valid session → continue
264
+ │ │
265
+ │ └── Not found or expired
266
+ │ │
267
+ │ ▼
268
+ │ aws sso login --profile <name>
269
+ │ (opens browser for SSO auth)
270
+ │ │
271
+ │ ▼
272
+ │ Poll ~/.aws/sso/cache/ every 2s
273
+ │ (up to 5 min timeout)
274
+ │
275
+ ▼
276
+ getSsoCredentials(profile)
277
+ │
278
+ ▼
279
+ aws configure list --profile <name>
280
+ (populates ~/.aws/cli/cache/)
281
+ │
282
+ ▼
283
+ getSsoCredsFromCacheFile(keyEnd, secretEnd)
284
+ │
285
+ ▼
286
+ Search ~/.aws/cli/cache/*.json
287
+ for matching credentials
288
+ │
289
+ ├── Found → return credentials
290
+ │
291
+ └── Not found (null)
292
+ │
293
+ ▼
294
+ Fallback: aws configure export-credentials
295
+ --profile <name> --format process
296
+ (directly outputs credentials as JSON)
297
+ │
298
+ ├── Success → return credentials
299
+ │
300
+ └── Failure → throw error
301
+ │
302
+ ▼
303
+ Return { aws_access_key_id,
304
+ aws_secret_access_key,
305
+ aws_session_token,
306
+ expiry_date }
307
+ ```
308
+
309
+ ## Expiry Handling
310
+
311
+ The tool uses a 2-minute buffer for expiry checks throughout. A session or credential is considered expired if it expires within the next 2 minutes:
312
+
313
+ ```javascript
314
+ const credStillValid = (Date.now() - 2*60*1000) < new Date(expiresAt).getTime()
315
+ ```
316
+
317
+ This prevents credentials from expiring between the time they're fetched and the time they're used.
318
+
319
+ ## Profile Name Conventions
320
+
321
+ - In `~/.aws/config`: Non-default profiles use `[profile name]` syntax. The default uses `[default]`.
322
+ - In `~/.aws/credentials`: All profiles use `[name]` syntax (no `profile` prefix).
323
+ - Profile names must be: lowercase alphanumeric characters, dashes, and underscores. Minimum 2 characters.
@@ -0,0 +1,267 @@
1
+ # CLI Interface
2
+
3
+ This document provides a detailed walkthrough of every menu, prompt, and user flow in `switch-profile`.
4
+
5
+ ## Invocation
6
+
7
+ ```shell
8
+ npx switch-profile # Run via npx (no install)
9
+ switch-profile # If installed globally
10
+ npx switch-profile switch # Explicit command (same as default)
11
+ npx switch-profile --version # Print version number
12
+ ```
13
+
14
+ The `switch` command is the only command and runs automatically when no arguments are provided.
15
+
16
+ ## Main Flow
17
+
18
+ ### Step 1: AWS CLI Check
19
+
20
+ Before anything else, the tool verifies AWS CLI v2+ is installed. If not found, it prints platform-specific installation instructions:
21
+
22
+ - **macOS:** `brew install awscli && brew link --overwrite awscli`
23
+ - **Linux:** curl + unzip instructions for the AWS CLI v2 installer
24
+ - **Windows:** Generic "not installed" message
25
+
26
+ ### Step 2: Default Profile Status
27
+
28
+ If a default profile is set, it displays the current status with color-coded information:
29
+
30
+ ```
31
+ Current default profile: sso-dev
32
+ INFO: This profile expires in 45.23 minutes # Cyan - healthy
33
+ ```
34
+
35
+ ```
36
+ Current default profile: sso-dev
37
+ WARNING: Expires in less than 2 minutes # Yellow - expiring soon
38
+ ```
39
+
40
+ ```
41
+ Current default profile: sso-dev
42
+ WARNING: Expired # Yellow - expired
43
+ ```
44
+
45
+ ```
46
+ Current default profile: unknown (pick one up in the list below...) # No profile set
47
+ ```
48
+
49
+ The expiry time is calculated in minutes to two decimal places.
50
+
51
+ ### Step 3: Profile List
52
+
53
+ If profiles exist, the main selection menu appears:
54
+
55
+ ```
56
+ ? Choose one of the following 3 profiles:
57
+ More options
58
+ Abort
59
+ ──────────────
60
+ 1. sso-dev (SSO [role:Admin - account:123456789012])
61
+ 2. sso-prod (SSO [role:ReadOnly - account:987654321098])
62
+ 3. my-standard-profile
63
+ ```
64
+
65
+ - **SSO profiles** include role and account info in their display name.
66
+ - **Standard profiles** show just the profile name.
67
+ - The list supports up to 20 items per page.
68
+
69
+ If no profiles exist, you get:
70
+
71
+ ```
72
+ ? There no profiles yet. Do you wish to create one now? (Y/n)
73
+ ```
74
+
75
+ ### Step 4a: Select a Profile
76
+
77
+ Selecting a profile from the list sets it as the `default`. For SSO profiles, this may:
78
+
79
+ 1. Open your browser for SSO authentication (if the session expired).
80
+ 2. Wait for you to complete the login (up to 5 minutes).
81
+ 3. Retrieve temporary credentials from the AWS CLI cache.
82
+
83
+ On success:
84
+
85
+ ```
86
+ AWS profile sso-dev successfully set up as default. # Green
87
+ ```
88
+
89
+ ### Step 4b: More Options
90
+
91
+ Selecting "More options" opens a submenu:
92
+
93
+ ```
94
+ ? Options:
95
+ Refresh default profile sso-dev # Only shown when expired
96
+ Create profile
97
+ Delete profiles
98
+ Abort
99
+ ```
100
+
101
+ ## Create Profile Flow
102
+
103
+ ### Step 1: Profile Name
104
+
105
+ ```
106
+ ? Enter a profile name (alphanumerical lowercase and '-' characters only):
107
+ ```
108
+
109
+ **Validation rules:**
110
+ - Cannot be empty.
111
+ - Only lowercase letters, numbers, dashes (`-`), and underscores (`_`).
112
+ - Minimum 2 characters.
113
+ - Cannot duplicate an existing profile name.
114
+
115
+ Invalid input produces a red error and re-prompts.
116
+
117
+ ### Step 2: Profile Type
118
+
119
+ ```
120
+ ? Choose an AWS profile type:
121
+ standard
122
+ sso
123
+ ```
124
+
125
+ ### Step 3a: Standard Profile
126
+
127
+ ```
128
+ ? Enter the profile's access key: AKIA...
129
+ ? Enter the profile's access secret key: wJal...
130
+ ? Select a region: (type to search)
131
+ us-east-2 - US East (Ohio)
132
+ us-east-1 - US East (N. Virginia)
133
+ us-west-1 - US West (N. California)
134
+ ...
135
+ ```
136
+
137
+ The region picker supports autocomplete - type to filter the list of 24 regions.
138
+
139
+ ### Step 3b: SSO Profile
140
+
141
+ Selecting "sso" first displays an SSO Setup Guide with guidance on the prompts you're about to see:
142
+
143
+ ```
144
+ ┌─────────────────────────────────────────────────────────────────────┐
145
+ │ SSO Profile Setup Guide │
146
+ ├─────────────────────────────────────────────────────────────────────┤
147
+ │ │
148
+ │ You're about to run 'aws configure sso'. Here's what to expect: │
149
+ │ │
150
+ │ 1. SSO session name │
151
+ │ Provide a name (e.g., "my-company-sso"). │
152
+ │ This enables token reuse across profiles and automatic refresh. │
153
+ │ Skipping this uses the legacy format (no auto-refresh). │
154
+ │ │
155
+ │ 2. SSO start URL │
156
+ │ Your AWS SSO portal URL (e.g., https://my-co.awsapps.com/start) │
157
+ │ Ask your AWS administrator if you don't have it. │
158
+ │ │
159
+ │ 3. SSO region │
160
+ │ The region where your SSO instance is configured. │
161
+ │ WARNING: This is NOT your deployment region. │
162
+ │ Using the wrong region causes "invalid_grant" errors. │
163
+ │ │
164
+ │ Docs: https://docs.aws.amazon.com/cli/latest/userguide/ │
165
+ │ cli-configure-sso.html │
166
+ │ │
167
+ └─────────────────────────────────────────────────────────────────────┘
168
+ ```
169
+
170
+ Then it spawns the interactive `aws configure sso` command, which takes over the terminal:
171
+
172
+ ```
173
+ SSO session name (Recommended): my-session
174
+ SSO start URL [None]: https://my-company.awsapps.com/start
175
+ SSO region [None]: us-east-1
176
+ SSO registration scopes [sso:account:access]:
177
+ ...
178
+ ```
179
+
180
+ This is the standard AWS CLI SSO setup flow. The user completes it directly. If the `aws configure sso` command fails (non-zero exit code), the error is reported and the profile is not marked as created.
181
+
182
+ ### Step 4: Set as Default
183
+
184
+ After creation, you're asked:
185
+
186
+ ```
187
+ ? Do you wish to set this new profile as the default? (Y/n)
188
+ ```
189
+
190
+ If yes, the profile is immediately set as the `default` (triggering SSO login if needed). If the profile is not found in `~/.aws/config` (e.g., because the creation partially failed), a clear error message is shown instead of crashing.
191
+
192
+ ## Delete Profiles Flow
193
+
194
+ ### Step 1: Select Profiles
195
+
196
+ ```
197
+ ? Select the profiles you which to delete:
198
+ ◯ 1. sso-dev (SSO [role:Admin - account:123456789012])
199
+ ◯ 2. sso-prod (SSO [role:ReadOnly - account:987654321098])
200
+ ◯ 3. my-standard-profile
201
+ ```
202
+
203
+ This is a checkbox selection - you can select multiple profiles.
204
+
205
+ ### Step 2: Confirmation
206
+
207
+ ```
208
+ ? Are you sure you want to delete those 2 profiles? (Y/n)
209
+ ```
210
+
211
+ ### Step 3: Validation
212
+
213
+ If you try to delete the current default profile:
214
+
215
+ ```
216
+ ERROR - Fail to delete profiles. Profile sso-dev is the current default.
217
+ Set another profile as the default, then try deleting again.
218
+ ```
219
+
220
+ You must switch to a different profile first.
221
+
222
+ ### Step 4: Success
223
+
224
+ ```
225
+ AWS profiles successfully deleted. # Green
226
+ ```
227
+
228
+ ## Refresh Default Profile
229
+
230
+ This option only appears in the "More options" submenu when the current default profile is expired.
231
+
232
+ Selecting it:
233
+ 1. Looks up the current default profile in the profile list.
234
+ 2. Retrieves fresh credentials (SSO login if needed).
235
+ 3. Updates the `[default]` section.
236
+
237
+ ```
238
+ AWS profile sso-dev successfully refreshed. # Green
239
+ ```
240
+
241
+ ## Visual Indicators
242
+
243
+ | Color | Meaning |
244
+ |-------|---------|
245
+ | **Green** | Success messages |
246
+ | **Red** | Error messages |
247
+ | **Cyan** | Informational (current profile, time remaining) |
248
+ | **Yellow** | Warnings (expired, expiring soon) |
249
+ | **Bold** | Profile names in messages |
250
+
251
+ ## Error Display
252
+
253
+ Errors are displayed in red with contextual messages:
254
+
255
+ ```
256
+ ERROR - Fail to get credentials for profile sso-dev
257
+ Fail to refresh the SSO session for AWS profile sso-dev
258
+ Timeout - Time to wait for refreshing the SSO session exceeded 300000ms.
259
+ ```
260
+
261
+ When the AWS CLI is missing, a helpful suggestion is appended:
262
+
263
+ ```
264
+ ERROR - Command aws not found
265
+
266
+ To fix this issue, try installing the aws CLI
267
+ ```