claude-aws-mfa 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -22,6 +22,11 @@ Releases are automated via CI. To create a new release:
22
22
  Stable versions (e.g. `v1.2.0`) publish to npm `@latest`. Pre-release versions
23
23
  (e.g. `v1.2.0-rc.1`) publish to npm `@next` and create a GitHub pre-release.
24
24
 
25
+ ## [v1.3.0] - 2026-10-14
26
+
27
+ ### Added
28
+ - Configurable STS session name in the credentials dialog (defaults to `claude-aws-mfa`).
29
+
25
30
  ## [v1.2.2] - 2026-10-09
26
31
 
27
32
  ### Changed
package/README.md CHANGED
@@ -47,7 +47,7 @@ Add this (plus any addtitional config you prefer) to your Claude Code settings (
47
47
  ## How it works
48
48
 
49
49
  1. On first run, seeds the dialog with credentials from your existing AWS config (`~/.aws/credentials`, environment variables, SSO, etc.)
50
- 2. Shows a native GUI dialog with fields for region, access key, secret key, MFA ARN, role ARN, and session duration
50
+ 2. Shows a native GUI dialog with fields for region, access key, secret key, MFA ARN, role ARN, session duration, and STS session name (defaults to `claude-aws-mfa`)
51
51
  3. Collects the MFA token — either type a 6-digit code directly, or switch to **MFA Command** mode and provide a shell command that outputs the code (e.g. `op item get --otp …` for 1Password CLI)
52
52
  4. Calls `sts:AssumeRole` with the MFA token
53
53
  5. If the requested session duration is rejected, automatically retries with shorter durations (12h → 6h → 2h → 1h)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-aws-mfa",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "repository": "https://github.com/aaronmeriwether/claude-aws-mfa",
6
6
  "bin": {
package/src/config.ts CHANGED
@@ -28,6 +28,7 @@ export interface Config {
28
28
  mfaArn: string;
29
29
  roleArn: string;
30
30
  duration: number;
31
+ sessionName?: string;
31
32
  mfaMode?: "code" | "command";
32
33
  mfaCommand?: string;
33
34
  cacheSession?: boolean;
@@ -1,8 +1,8 @@
1
- import { STANDARD_DURATIONS } from "./sts";
1
+ import { DEFAULT_SESSION_NAME, STANDARD_DURATIONS } from "./sts";
2
2
  import { AUTOSIZE_CSS, AUTOSIZE_SCRIPT } from "./autosize-html";
3
3
  import { DEFAULT_PROFILE, type ActiveProfile } from "./config";
4
4
 
5
- export const FIELDS = ["region", "accessKeyId", "secretAccessKey", "mfaArn", "roleArn", "duration", "mfaMode", "mfaCode", "mfaCommand"] as const;
5
+ export const FIELDS = ["region", "accessKeyId", "secretAccessKey", "mfaArn", "roleArn", "duration", "sessionName", "mfaMode", "mfaCode", "mfaCommand"] as const;
6
6
 
7
7
  export const FIELD_PATTERNS: Record<string, string> = {
8
8
  region: "^[a-z]{2}(-[a-z]+-\\d+)$",
@@ -11,6 +11,8 @@ export const FIELD_PATTERNS: Record<string, string> = {
11
11
  mfaArn: "^arn:aws:iam::\\d{12}:mfa/.+$",
12
12
  roleArn: "^arn:aws:iam::\\d{12}:role/.+$",
13
13
  duration: "^[1-9]\\d*$",
14
+ // AWS RoleSessionName constraint.
15
+ sessionName: "^[\\w+=,.@-]{2,64}$",
14
16
  mfaCode: "^\\d{6}$",
15
17
  mfaCommand: "^.+$",
16
18
  };
@@ -90,6 +92,7 @@ ${PROFILE_STYLE}
90
92
  <div class="field"><label>MFA Device ARN</label><input id="mfaArn" placeholder="arn:aws:iam::123456789012:mfa/user"></div>
91
93
  <div class="field"><label>Role ARN</label><input id="roleArn" placeholder="arn:aws:iam::123456789012:role/MyRole"></div>
92
94
  <div class="field"><label>Session Duration (seconds)</label><input id="duration" placeholder="${STANDARD_DURATIONS[0]}"></div>
95
+ <div class="field"><label>Session Name</label><input id="sessionName" placeholder="${DEFAULT_SESSION_NAME}"></div>
93
96
  <hr>
94
97
  <div class="radio-field">
95
98
  <input type="radio" name="mfaMode" id="mfaModeCode" value="code" checked>
package/src/dialog.ts CHANGED
@@ -3,6 +3,7 @@ import { lib } from "webview-bun/src/ffi";
3
3
  import { activeProfile, type Config } from "./config";
4
4
  import { buildHtml, buildErrorHtml } from "./dialog-html";
5
5
  import { START_HEIGHT, bindAutosize } from "./autosize";
6
+ import { DEFAULT_SESSION_NAME } from "./sts";
6
7
  import { version } from "../package.json";
7
8
 
8
9
  const DIALOG_WIDTH = 440;
@@ -24,6 +25,7 @@ export interface DialogResult {
24
25
  mfaArn: string;
25
26
  roleArn: string;
26
27
  duration: string;
28
+ sessionName: string;
27
29
  mfaMode: "code" | "command";
28
30
  mfaCode: string;
29
31
  mfaCommand: string;
@@ -81,6 +83,7 @@ export function showDialog(defaults: Partial<Config>): DialogResult | null {
81
83
  mfaArn: defaults.mfaArn ?? "",
82
84
  roleArn: defaults.roleArn ?? "",
83
85
  duration: defaults.duration != null ? String(defaults.duration) : "",
86
+ sessionName: defaults.sessionName ?? DEFAULT_SESSION_NAME,
84
87
  mfaCommand: defaults.mfaCommand ?? "",
85
88
  mfaMode: defaults.mfaMode ?? "code",
86
89
  }, activeProfile()));
package/src/index.ts CHANGED
@@ -92,6 +92,7 @@ async function tryAutoMfa(cfg: Partial<Config>): Promise<CachedSession | null> {
92
92
  roleArn: cfg.roleArn,
93
93
  mfaCode,
94
94
  duration: cfg.duration ?? STANDARD_DURATIONS[0],
95
+ sessionName: cfg.sessionName,
95
96
  });
96
97
 
97
98
  const session: CachedSession = {
@@ -108,6 +109,7 @@ async function tryAutoMfa(cfg: Partial<Config>): Promise<CachedSession | null> {
108
109
  mfaArn: cfg.mfaArn,
109
110
  roleArn: cfg.roleArn,
110
111
  duration,
112
+ sessionName: cfg.sessionName,
111
113
  mfaMode: cfg.mfaMode,
112
114
  mfaCommand: cfg.mfaCommand,
113
115
  cacheSession: cfg.cacheSession,
@@ -146,6 +148,7 @@ async function obtainViaDialog(cfg: Partial<Config>): Promise<void> {
146
148
  roleArn: result.roleArn,
147
149
  mfaCode,
148
150
  duration: parseInt(result.duration, 10) || STANDARD_DURATIONS[0],
151
+ sessionName: result.sessionName,
149
152
  });
150
153
 
151
154
  const session: CachedSession = {
@@ -162,6 +165,7 @@ async function obtainViaDialog(cfg: Partial<Config>): Promise<void> {
162
165
  mfaArn: result.mfaArn,
163
166
  roleArn: result.roleArn,
164
167
  duration,
168
+ sessionName: result.sessionName || undefined,
165
169
  mfaMode: result.mfaMode,
166
170
  mfaCommand: result.mfaCommand || undefined,
167
171
  cacheSession: defaults.cacheSession,
package/src/sts.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
2
2
 
3
+ export const DEFAULT_SESSION_NAME = "claude-aws-mfa";
4
+
3
5
  export const STANDARD_DURATIONS = [129600, 86400, 43200, 28800, 21600, 14400, 7200, 3600];
4
6
 
5
7
  export function isDurationError(err: unknown): boolean {
@@ -25,6 +27,7 @@ export async function assumeRoleWithMfa(params: {
25
27
  roleArn: string;
26
28
  mfaCode: string;
27
29
  duration: number;
30
+ sessionName?: string;
28
31
  }) {
29
32
  const client = new STSClient({
30
33
  region: params.region,
@@ -37,7 +40,7 @@ export async function assumeRoleWithMfa(params: {
37
40
  try {
38
41
  const { Credentials: c } = await client.send(new AssumeRoleCommand({
39
42
  RoleArn: params.roleArn,
40
- RoleSessionName: "claude-aws-mfa",
43
+ RoleSessionName: params.sessionName || DEFAULT_SESSION_NAME,
41
44
  SerialNumber: params.mfaArn,
42
45
  TokenCode: params.mfaCode,
43
46
  DurationSeconds: ladder[i],
@@ -103,7 +103,7 @@ describe("buildHtml", () => {
103
103
  test("FIELDS list matches expected form fields", () => {
104
104
  expect(FIELDS).toEqual([
105
105
  "region", "accessKeyId", "secretAccessKey",
106
- "mfaArn", "roleArn", "duration", "mfaMode", "mfaCode", "mfaCommand",
106
+ "mfaArn", "roleArn", "duration", "sessionName", "mfaMode", "mfaCode", "mfaCommand",
107
107
  ]);
108
108
  });
109
109
 
@@ -128,6 +128,7 @@ describe("buildHtml", () => {
128
128
  mfaArn: "arn:aws:iam::123456789012:mfa/user",
129
129
  roleArn: "arn:aws:iam::123456789012:role/TestRole",
130
130
  duration: "43200",
131
+ sessionName: "claude-aws-mfa",
131
132
  mfaCode: "123456",
132
133
  mfaCommand: "op item get --otp myitem",
133
134
  };
@@ -138,6 +139,7 @@ describe("buildHtml", () => {
138
139
  mfaArn: "not-an-arn",
139
140
  roleArn: "not-an-arn",
140
141
  duration: "abc",
142
+ sessionName: "has spaces",
141
143
  mfaCode: "12",
142
144
  mfaCommand: "",
143
145
  };
package/test/sts.test.ts CHANGED
@@ -117,6 +117,12 @@ describe("assumeRoleWithMfa", () => {
117
117
  });
118
118
  });
119
119
 
120
+ test("passes a custom session name to STS", async () => {
121
+ await assumeRoleWithMfa({ ...BASE_PARAMS, sessionName: "alice-laptop" });
122
+
123
+ expect(capturedCommands[0].RoleSessionName).toBe("alice-laptop");
124
+ });
125
+
120
126
  test("falls back to shorter duration on duration error", async () => {
121
127
  let callCount = 0;
122
128
  mockSend = mock(() => {