claude-aws-mfa 1.1.0 → 1.2.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/.github/workflows/release.yml +86 -37
- package/CHANGELOG.md +16 -0
- package/README.md +30 -6
- package/package.json +1 -1
- package/src/autosize-html.ts +31 -0
- package/src/autosize.ts +39 -0
- package/src/claude-settings.ts +13 -5
- package/src/config.ts +80 -3
- package/src/credential-export.ts +32 -0
- package/src/dialog-html.ts +76 -3
- package/src/dialog.ts +36 -6
- package/src/flags.ts +5 -0
- package/src/index.ts +83 -55
- package/src/setup-html.ts +16 -3
- package/src/setup.ts +12 -15
- package/test/autosize.test.ts +56 -0
- package/test/config.test.ts +77 -0
- package/test/dialog-html.test.ts +53 -1
- package/test/index.test.ts +5 -0
- package/test/setup.test.ts +59 -0
package/test/dialog-html.test.ts
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import { buildHtml, FIELDS, FIELD_PATTERNS } from "../src/dialog-html";
|
|
2
|
+
import { buildHtml, buildErrorHtml, FIELDS, FIELD_PATTERNS } from "../src/dialog-html";
|
|
3
|
+
|
|
4
|
+
describe("profile banner", () => {
|
|
5
|
+
const profile = { name: "work", path: "~/.claude-profiles/work" };
|
|
6
|
+
|
|
7
|
+
test("is shown for the default profile too, never blank", () => {
|
|
8
|
+
for (const html of [buildHtml({}), buildErrorHtml("boom")]) {
|
|
9
|
+
expect(html).toContain('class="profile"');
|
|
10
|
+
expect(html).toContain("<b>default</b>");
|
|
11
|
+
expect(html).toContain("~/.claude");
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("names the profile in both the credential and error dialogs", () => {
|
|
16
|
+
for (const html of [buildHtml({}, profile), buildErrorHtml("boom", profile)]) {
|
|
17
|
+
expect(html).toContain('class="profile"');
|
|
18
|
+
expect(html).toContain("<b>work</b>");
|
|
19
|
+
expect(html).toContain("~/.claude-profiles/work");
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("escapes the profile name and path", () => {
|
|
24
|
+
const html = buildHtml({}, { name: '<img src=x onerror="alert(1)">', path: "/a&b" });
|
|
25
|
+
expect(html).not.toContain("<img src=x");
|
|
26
|
+
expect(html).toContain("<img");
|
|
27
|
+
expect(html).toContain("/a&b");
|
|
28
|
+
});
|
|
29
|
+
});
|
|
3
30
|
|
|
4
31
|
describe("buildHtml", () => {
|
|
5
32
|
const config = {
|
|
@@ -161,3 +188,28 @@ describe("buildHtml", () => {
|
|
|
161
188
|
expect(html).toContain("currentMode()");
|
|
162
189
|
});
|
|
163
190
|
});
|
|
191
|
+
|
|
192
|
+
describe("buildErrorHtml", () => {
|
|
193
|
+
test("produces valid HTML with doctype", () => {
|
|
194
|
+
const html = buildErrorHtml("Something went wrong");
|
|
195
|
+
expect(html).toStartWith("<!DOCTYPE html>");
|
|
196
|
+
expect(html).toContain("</html>");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("includes the message text", () => {
|
|
200
|
+
const html = buildErrorHtml("STS AssumeRole failed: AccessDenied");
|
|
201
|
+
expect(html).toContain("STS AssumeRole failed: AccessDenied");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("has an OK button", () => {
|
|
205
|
+
const html = buildErrorHtml("error");
|
|
206
|
+
expect(html).toContain("_ok()");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("escapes HTML special characters in the message", () => {
|
|
210
|
+
const html = buildErrorHtml('<script>alert(1)</script> & "quoted"');
|
|
211
|
+
expect(html).not.toContain("<script>alert(1)</script>");
|
|
212
|
+
expect(html).toContain("<script>");
|
|
213
|
+
expect(html).toContain("&");
|
|
214
|
+
});
|
|
215
|
+
});
|
package/test/index.test.ts
CHANGED
|
@@ -11,6 +11,11 @@ describe("parseFlags", () => {
|
|
|
11
11
|
expect(flags.cacheSession).toBeUndefined();
|
|
12
12
|
expect(flags.autoMfa).toBeUndefined();
|
|
13
13
|
expect(flags.singleInstanceLock).toBeUndefined();
|
|
14
|
+
expect(flags.noUi).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("parses --no-ui", () => {
|
|
18
|
+
expect(parseFlags([...base, "--no-ui"]).noUi).toBe(true);
|
|
14
19
|
});
|
|
15
20
|
|
|
16
21
|
test("parses --setup", () => {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, test, expect, afterEach } from "bun:test";
|
|
2
|
+
import { mkdtempSync, rmSync, symlinkSync, writeFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { getCredentialExportCommand } from "../src/credential-export";
|
|
6
|
+
import { bin } from "../package.json";
|
|
7
|
+
|
|
8
|
+
const BIN_NAME = Object.keys(bin)[0];
|
|
9
|
+
|
|
10
|
+
describe("getCredentialExportCommand", () => {
|
|
11
|
+
const origArgv = Bun.argv;
|
|
12
|
+
const origWhich = Bun.which;
|
|
13
|
+
let tmpDir: string;
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
Bun.argv = origArgv;
|
|
17
|
+
Bun.which = origWhich;
|
|
18
|
+
if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("uses the bare bin name when it resolves via PATH to this same script (bin invoked directly)", () => {
|
|
22
|
+
tmpDir = mkdtempSync(join(tmpdir(), "claude-aws-mfa-setup-test-"));
|
|
23
|
+
const scriptTarget = join(tmpDir, "index.ts");
|
|
24
|
+
writeFileSync(scriptTarget, "");
|
|
25
|
+
// Mirrors real installed-bin behavior: the shim on PATH is a symlink whose
|
|
26
|
+
// target Bun resolves into Bun.argv[1].
|
|
27
|
+
const shim = join(tmpDir, BIN_NAME);
|
|
28
|
+
symlinkSync(scriptTarget, shim);
|
|
29
|
+
|
|
30
|
+
Bun.argv = [process.execPath, scriptTarget];
|
|
31
|
+
Bun.which = ((cmd: string) => (cmd === BIN_NAME ? shim : null)) as typeof Bun.which;
|
|
32
|
+
|
|
33
|
+
expect(getCredentialExportCommand()).toBe(BIN_NAME);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("falls back to an explicit bun+script invocation when not resolvable via PATH", () => {
|
|
37
|
+
tmpDir = mkdtempSync(join(tmpdir(), "claude-aws-mfa-setup-test-"));
|
|
38
|
+
const scriptTarget = join(tmpDir, "index.ts");
|
|
39
|
+
writeFileSync(scriptTarget, "");
|
|
40
|
+
|
|
41
|
+
Bun.argv = [process.execPath, scriptTarget];
|
|
42
|
+
Bun.which = (() => null) as typeof Bun.which;
|
|
43
|
+
|
|
44
|
+
expect(getCredentialExportCommand()).toBe(`${process.execPath} ${scriptTarget}`);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("falls back when PATH resolves the bin name to a different install", () => {
|
|
48
|
+
tmpDir = mkdtempSync(join(tmpdir(), "claude-aws-mfa-setup-test-"));
|
|
49
|
+
const scriptTarget = join(tmpDir, "index.ts");
|
|
50
|
+
const otherInstall = join(tmpDir, "other-index.ts");
|
|
51
|
+
writeFileSync(scriptTarget, "");
|
|
52
|
+
writeFileSync(otherInstall, "");
|
|
53
|
+
|
|
54
|
+
Bun.argv = [process.execPath, scriptTarget];
|
|
55
|
+
Bun.which = ((cmd: string) => (cmd === BIN_NAME ? otherInstall : null)) as typeof Bun.which;
|
|
56
|
+
|
|
57
|
+
expect(getCredentialExportCommand()).toBe(`${process.execPath} ${scriptTarget}`);
|
|
58
|
+
});
|
|
59
|
+
});
|