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 +5 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/config.ts +1 -0
- package/src/dialog-html.ts +5 -2
- package/src/dialog.ts +3 -0
- package/src/index.ts +4 -0
- package/src/sts.ts +4 -1
- package/test/dialog-html.test.ts +3 -1
- package/test/sts.test.ts +6 -0
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
|
|
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
package/src/config.ts
CHANGED
package/src/dialog-html.ts
CHANGED
|
@@ -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:
|
|
43
|
+
RoleSessionName: params.sessionName || DEFAULT_SESSION_NAME,
|
|
41
44
|
SerialNumber: params.mfaArn,
|
|
42
45
|
TokenCode: params.mfaCode,
|
|
43
46
|
DurationSeconds: ladder[i],
|
package/test/dialog-html.test.ts
CHANGED
|
@@ -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(() => {
|