@reegaviljoen/eldlock 0.1.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 +285 -0
- package/bin/eldlock +11 -0
- package/docs/architecture.md +164 -0
- package/docs/threat-model.md +47 -0
- package/eldlock-cli/README.md +56 -0
- package/eldlock-cli/bin/eldlock +3 -0
- package/eldlock-cli/package-lock.json +805 -0
- package/eldlock-cli/package.json +71 -0
- package/eldlock-cli/src/api.ts +250 -0
- package/eldlock-cli/src/cli.ts +490 -0
- package/eldlock-cli/src/main.ts +10 -0
- package/eldlock-cli/src/tui.ts +676 -0
- package/eldlock-cli/tsconfig.json +13 -0
- package/eldlock-cli/vendor/npm/ansi-regex-6.2.2.tgz +0 -0
- package/eldlock-cli/vendor/npm/bun-ffi-structs-0.2.2.tgz +0 -0
- package/eldlock-cli/vendor/npm/diff-9.0.0.tgz +0 -0
- package/eldlock-cli/vendor/npm/emoji-regex-10.6.0.tgz +0 -0
- package/eldlock-cli/vendor/npm/esbuild-0.28.0.tgz +0 -0
- package/eldlock-cli/vendor/npm/esbuild-darwin-arm64-0.28.0.tgz +0 -0
- package/eldlock-cli/vendor/npm/esbuild-darwin-x64-0.28.0.tgz +0 -0
- package/eldlock-cli/vendor/npm/esbuild-linux-arm64-0.28.0.tgz +0 -0
- package/eldlock-cli/vendor/npm/esbuild-linux-x64-0.28.0.tgz +0 -0
- package/eldlock-cli/vendor/npm/fsevents-2.3.3.tgz +0 -0
- package/eldlock-cli/vendor/npm/get-east-asian-width-1.6.0.tgz +0 -0
- package/eldlock-cli/vendor/npm/marked-17.0.1.tgz +0 -0
- package/eldlock-cli/vendor/npm/opentui-core-0.3.1.tgz +0 -0
- package/eldlock-cli/vendor/npm/opentui-core-darwin-arm64-0.3.1.tgz +0 -0
- package/eldlock-cli/vendor/npm/opentui-core-darwin-x64-0.3.1.tgz +0 -0
- package/eldlock-cli/vendor/npm/opentui-core-linux-arm64-0.3.1.tgz +0 -0
- package/eldlock-cli/vendor/npm/opentui-core-linux-x64-0.3.1.tgz +0 -0
- package/eldlock-cli/vendor/npm/string-width-7.2.0.tgz +0 -0
- package/eldlock-cli/vendor/npm/strip-ansi-7.1.2.tgz +0 -0
- package/eldlock-cli/vendor/npm/tsx-4.22.4.tgz +0 -0
- package/eldlock-cli/vendor/npm/types-node-22.19.19.tgz +0 -0
- package/eldlock-cli/vendor/npm/typescript-5.9.3.tgz +0 -0
- package/eldlock-cli/vendor/npm/undici-types-6.21.0.tgz +0 -0
- package/eldlock-cli/vendor/npm/web-tree-sitter-0.25.10.tgz +0 -0
- package/eldlock-cli/vendor/npm/yoga-layout-3.2.1.tgz +0 -0
- package/eldlock-server/cmd/eldlock-server/main.go +132 -0
- package/eldlock-server/go.mod +10 -0
- package/eldlock-server/go.sum +11 -0
- package/eldlock-server/internal/api/README.md +14 -0
- package/eldlock-server/internal/api/core.go +126 -0
- package/eldlock-server/internal/api/exec.go +97 -0
- package/eldlock-server/internal/api/secrets.go +358 -0
- package/eldlock-server/internal/api/server.go +72 -0
- package/eldlock-server/internal/api/service_test.go +416 -0
- package/eldlock-server/internal/api/types.go +48 -0
- package/eldlock-server/internal/api/vault.go +69 -0
- package/eldlock-server/internal/api/vendor.go +44 -0
- package/eldlock-server/internal/libfido2/LICENSE +21 -0
- package/eldlock-server/internal/libfido2/README.md +127 -0
- package/eldlock-server/internal/libfido2/examples_test.go +614 -0
- package/eldlock-server/internal/libfido2/fido2.go +1234 -0
- package/eldlock-server/internal/libfido2/fido2_darwin.go +7 -0
- package/eldlock-server/internal/libfido2/fido2_other.go +9 -0
- package/eldlock-server/internal/libfido2/fido2_test.go +101 -0
- package/eldlock-server/internal/libfido2/go.mod +10 -0
- package/eldlock-server/internal/libfido2/go.sum +16 -0
- package/eldlock-server/internal/libfido2/log.go +87 -0
- package/eldlock-server/internal/store/README.md +7 -0
- package/eldlock-server/internal/store/store.go +434 -0
- package/eldlock-server/internal/store/store_test.go +125 -0
- package/eldlock-server/internal/yubikey/README.md +25 -0
- package/eldlock-server/internal/yubikey/default_fido2.go +7 -0
- package/eldlock-server/internal/yubikey/default_stub.go +7 -0
- package/eldlock-server/internal/yubikey/fido2_disabled.go +9 -0
- package/eldlock-server/internal/yubikey/fido2_libfido2.go +225 -0
- package/eldlock-server/internal/yubikey/fido2_libfido2_test.go +66 -0
- package/eldlock-server/internal/yubikey/passkey.go +139 -0
- package/eldlock-server/internal/yubikey/passkey_test.go +36 -0
- package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/LICENSE +21 -0
- package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/README.md +127 -0
- package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/fido2.go +1234 -0
- package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/fido2_darwin.go +7 -0
- package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/fido2_other.go +9 -0
- package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/log.go +87 -0
- package/eldlock-server/vendor/github.com/pkg/errors/.travis.yml +10 -0
- package/eldlock-server/vendor/github.com/pkg/errors/LICENSE +23 -0
- package/eldlock-server/vendor/github.com/pkg/errors/Makefile +44 -0
- package/eldlock-server/vendor/github.com/pkg/errors/README.md +59 -0
- package/eldlock-server/vendor/github.com/pkg/errors/appveyor.yml +32 -0
- package/eldlock-server/vendor/github.com/pkg/errors/errors.go +288 -0
- package/eldlock-server/vendor/github.com/pkg/errors/go113.go +38 -0
- package/eldlock-server/vendor/github.com/pkg/errors/stack.go +177 -0
- package/eldlock-server/vendor/modules.txt +7 -0
- package/examples/eldlock.toml +17 -0
- package/install.sh +66 -0
- package/package.json +66 -0
- package/scripts/build-production.mjs +177 -0
- package/scripts/postinstall-production.mjs +23 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eldlock-cli",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"eldlock": "./dist/main.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "node scripts/build.mjs",
|
|
11
|
+
"build:dev": "node scripts/build.mjs --dev",
|
|
12
|
+
"dev": "tsx src/main.ts",
|
|
13
|
+
"install:vendored": "npm ci --offline",
|
|
14
|
+
"typecheck": "tsc --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@opentui/core": "file:vendor/npm/opentui-core-0.3.1.tgz",
|
|
18
|
+
"ansi-regex": "file:vendor/npm/ansi-regex-6.2.2.tgz",
|
|
19
|
+
"bun-ffi-structs": "file:vendor/npm/bun-ffi-structs-0.2.2.tgz",
|
|
20
|
+
"diff": "file:vendor/npm/diff-9.0.0.tgz",
|
|
21
|
+
"emoji-regex": "file:vendor/npm/emoji-regex-10.6.0.tgz",
|
|
22
|
+
"get-east-asian-width": "file:vendor/npm/get-east-asian-width-1.6.0.tgz",
|
|
23
|
+
"marked": "file:vendor/npm/marked-17.0.1.tgz",
|
|
24
|
+
"string-width": "file:vendor/npm/string-width-7.2.0.tgz",
|
|
25
|
+
"strip-ansi": "file:vendor/npm/strip-ansi-7.1.2.tgz",
|
|
26
|
+
"web-tree-sitter": "file:vendor/npm/web-tree-sitter-0.25.10.tgz",
|
|
27
|
+
"yoga-layout": "file:vendor/npm/yoga-layout-3.2.1.tgz"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "file:vendor/npm/types-node-22.19.19.tgz",
|
|
31
|
+
"esbuild": "file:vendor/npm/esbuild-0.28.0.tgz",
|
|
32
|
+
"tsx": "file:vendor/npm/tsx-4.22.4.tgz",
|
|
33
|
+
"typescript": "file:vendor/npm/typescript-5.9.3.tgz",
|
|
34
|
+
"undici-types": "file:vendor/npm/undici-types-6.21.0.tgz"
|
|
35
|
+
},
|
|
36
|
+
"optionalDependencies": {
|
|
37
|
+
"@esbuild/aix-ppc64": "file:vendor/npm/esbuild-aix-ppc64-0.28.0.tgz",
|
|
38
|
+
"@esbuild/android-arm": "file:vendor/npm/esbuild-android-arm-0.28.0.tgz",
|
|
39
|
+
"@esbuild/android-arm64": "file:vendor/npm/esbuild-android-arm64-0.28.0.tgz",
|
|
40
|
+
"@esbuild/android-x64": "file:vendor/npm/esbuild-android-x64-0.28.0.tgz",
|
|
41
|
+
"@esbuild/darwin-arm64": "file:vendor/npm/esbuild-darwin-arm64-0.28.0.tgz",
|
|
42
|
+
"@esbuild/darwin-x64": "file:vendor/npm/esbuild-darwin-x64-0.28.0.tgz",
|
|
43
|
+
"@esbuild/freebsd-arm64": "file:vendor/npm/esbuild-freebsd-arm64-0.28.0.tgz",
|
|
44
|
+
"@esbuild/freebsd-x64": "file:vendor/npm/esbuild-freebsd-x64-0.28.0.tgz",
|
|
45
|
+
"@esbuild/linux-arm": "file:vendor/npm/esbuild-linux-arm-0.28.0.tgz",
|
|
46
|
+
"@esbuild/linux-arm64": "file:vendor/npm/esbuild-linux-arm64-0.28.0.tgz",
|
|
47
|
+
"@esbuild/linux-ia32": "file:vendor/npm/esbuild-linux-ia32-0.28.0.tgz",
|
|
48
|
+
"@esbuild/linux-loong64": "file:vendor/npm/esbuild-linux-loong64-0.28.0.tgz",
|
|
49
|
+
"@esbuild/linux-mips64el": "file:vendor/npm/esbuild-linux-mips64el-0.28.0.tgz",
|
|
50
|
+
"@esbuild/linux-ppc64": "file:vendor/npm/esbuild-linux-ppc64-0.28.0.tgz",
|
|
51
|
+
"@esbuild/linux-riscv64": "file:vendor/npm/esbuild-linux-riscv64-0.28.0.tgz",
|
|
52
|
+
"@esbuild/linux-s390x": "file:vendor/npm/esbuild-linux-s390x-0.28.0.tgz",
|
|
53
|
+
"@esbuild/linux-x64": "file:vendor/npm/esbuild-linux-x64-0.28.0.tgz",
|
|
54
|
+
"@esbuild/netbsd-arm64": "file:vendor/npm/esbuild-netbsd-arm64-0.28.0.tgz",
|
|
55
|
+
"@esbuild/netbsd-x64": "file:vendor/npm/esbuild-netbsd-x64-0.28.0.tgz",
|
|
56
|
+
"@esbuild/openbsd-arm64": "file:vendor/npm/esbuild-openbsd-arm64-0.28.0.tgz",
|
|
57
|
+
"@esbuild/openbsd-x64": "file:vendor/npm/esbuild-openbsd-x64-0.28.0.tgz",
|
|
58
|
+
"@esbuild/openharmony-arm64": "file:vendor/npm/esbuild-openharmony-arm64-0.28.0.tgz",
|
|
59
|
+
"@esbuild/sunos-x64": "file:vendor/npm/esbuild-sunos-x64-0.28.0.tgz",
|
|
60
|
+
"@esbuild/win32-arm64": "file:vendor/npm/esbuild-win32-arm64-0.28.0.tgz",
|
|
61
|
+
"@esbuild/win32-ia32": "file:vendor/npm/esbuild-win32-ia32-0.28.0.tgz",
|
|
62
|
+
"@esbuild/win32-x64": "file:vendor/npm/esbuild-win32-x64-0.28.0.tgz",
|
|
63
|
+
"@opentui/core-darwin-arm64": "file:vendor/npm/opentui-core-darwin-arm64-0.3.1.tgz",
|
|
64
|
+
"@opentui/core-darwin-x64": "file:vendor/npm/opentui-core-darwin-x64-0.3.1.tgz",
|
|
65
|
+
"@opentui/core-linux-arm64": "file:vendor/npm/opentui-core-linux-arm64-0.3.1.tgz",
|
|
66
|
+
"@opentui/core-linux-x64": "file:vendor/npm/opentui-core-linux-x64-0.3.1.tgz",
|
|
67
|
+
"@opentui/core-win32-arm64": "file:vendor/npm/opentui-core-win32-arm64-0.3.1.tgz",
|
|
68
|
+
"@opentui/core-win32-x64": "file:vendor/npm/opentui-core-win32-x64-0.3.1.tgz",
|
|
69
|
+
"fsevents": "file:vendor/npm/fsevents-2.3.3.tgz"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import net from "node:net";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
|
|
8
|
+
export type SecretType = "env" | "ssh";
|
|
9
|
+
export type OutputMode = "plain" | "clipboard";
|
|
10
|
+
|
|
11
|
+
export type EldlockRequest = {
|
|
12
|
+
action: string;
|
|
13
|
+
vault?: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
type?: SecretType;
|
|
16
|
+
value?: string;
|
|
17
|
+
source_path?: string;
|
|
18
|
+
output?: OutputMode;
|
|
19
|
+
pin?: string;
|
|
20
|
+
command?: string[];
|
|
21
|
+
shell?: string;
|
|
22
|
+
shell_command?: string;
|
|
23
|
+
interactive?: boolean;
|
|
24
|
+
cwd?: string;
|
|
25
|
+
env?: Record<string, string>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type EldlockResponse = {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
message?: string;
|
|
31
|
+
value?: string;
|
|
32
|
+
stdout?: string;
|
|
33
|
+
stderr?: string;
|
|
34
|
+
exit_code?: number;
|
|
35
|
+
imported?: number;
|
|
36
|
+
env?: Record<string, string>;
|
|
37
|
+
secrets?: Array<{ name: string; type: SecretType }>;
|
|
38
|
+
error?: string;
|
|
39
|
+
code?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export async function requestWithPINPrompt(payload: EldlockRequest): Promise<EldlockResponse> {
|
|
43
|
+
let pin: string | undefined;
|
|
44
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
45
|
+
const response = await request(pin ? { ...payload, pin } : payload);
|
|
46
|
+
if (response.ok || (response.code !== "pin_required" && response.code !== "pin_invalid")) {
|
|
47
|
+
return response;
|
|
48
|
+
}
|
|
49
|
+
if (response.code === "pin_invalid") {
|
|
50
|
+
console.error("eldlock: PIN was invalid; try again.");
|
|
51
|
+
}
|
|
52
|
+
pin = await promptHidden("YubiKey PIN: ");
|
|
53
|
+
}
|
|
54
|
+
return { ok: false, code: "pin_invalid", error: "YubiKey PIN failed too many times" };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function request(payload: EldlockRequest): Promise<EldlockResponse> {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const chunks: Buffer[] = [];
|
|
60
|
+
const socket = net.createConnection(defaultSocketPath());
|
|
61
|
+
|
|
62
|
+
socket.on("connect", () => {
|
|
63
|
+
socket.write(`${JSON.stringify(payload)}\n`);
|
|
64
|
+
});
|
|
65
|
+
socket.on("data", (chunk: Buffer) => {
|
|
66
|
+
chunks.push(chunk);
|
|
67
|
+
if (chunk.includes(10)) {
|
|
68
|
+
socket.end();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
socket.on("error", (error: NodeJS.ErrnoException) => {
|
|
72
|
+
if (error.code === "ENOENT" || error.code === "ECONNREFUSED") {
|
|
73
|
+
reject(new Error(`could not connect to eldlock-server at ${defaultSocketPath()}; start it with "eldlock start"`));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
reject(error);
|
|
77
|
+
});
|
|
78
|
+
socket.on("end", () => {
|
|
79
|
+
try {
|
|
80
|
+
const line = Buffer.concat(chunks).toString("utf8").trim();
|
|
81
|
+
resolve(JSON.parse(line) as EldlockResponse);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
reject(error);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function checkServerHealth(): Promise<EldlockResponse> {
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
request({ action: "health" })
|
|
92
|
+
.then(resolve)
|
|
93
|
+
.catch((error: unknown) => {
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
resolve({ ok: false, error: message });
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function printResponse(response: EldlockResponse): void {
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
throw new Error(response.error ?? "request failed");
|
|
103
|
+
}
|
|
104
|
+
if (response.value !== undefined) {
|
|
105
|
+
process.stdout.write(response.value);
|
|
106
|
+
if (!response.value.endsWith("\n")) {
|
|
107
|
+
process.stdout.write("\n");
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (response.message) {
|
|
112
|
+
console.log(response.message);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function defaultSocketPath(): string {
|
|
117
|
+
if (process.env.ELDLOCK_SOCKET) {
|
|
118
|
+
return process.env.ELDLOCK_SOCKET;
|
|
119
|
+
}
|
|
120
|
+
const digest = crypto.createHash("sha256").update(defaultStateDir()).digest("hex").slice(0, 16);
|
|
121
|
+
return path.join(os.tmpdir(), "eldlock", digest, "daemon.sock");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function defaultStateDir(): string {
|
|
125
|
+
return process.env.ELDLOCK_STATE_DIR ?? path.join(process.cwd(), ".eldlock");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function defaultPidPath(): string {
|
|
129
|
+
return path.join(defaultStateDir(), "server.pid");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function defaultLogPath(): string {
|
|
133
|
+
return path.join(defaultStateDir(), "server.log");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function readPid(): number | undefined {
|
|
137
|
+
try {
|
|
138
|
+
const raw = fs.readFileSync(defaultPidPath(), "utf8").trim();
|
|
139
|
+
const pid = Number.parseInt(raw, 10);
|
|
140
|
+
return Number.isFinite(pid) ? pid : undefined;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function isPidAlive(pid: number): boolean {
|
|
150
|
+
try {
|
|
151
|
+
process.kill(pid, 0);
|
|
152
|
+
return true;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (isNodeError(error) && error.code === "ESRCH") {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (isNodeError(error) && error.code === "EPERM") {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function cleanupPidFile(): void {
|
|
165
|
+
try {
|
|
166
|
+
fs.unlinkSync(defaultPidPath());
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function waitForHealth(timeoutMs: number): Promise<EldlockResponse> {
|
|
176
|
+
const deadline = Date.now() + timeoutMs;
|
|
177
|
+
let last: EldlockResponse = { ok: false };
|
|
178
|
+
while (Date.now() < deadline) {
|
|
179
|
+
last = await checkServerHealth();
|
|
180
|
+
if (last.ok) {
|
|
181
|
+
return last;
|
|
182
|
+
}
|
|
183
|
+
await sleep(100);
|
|
184
|
+
}
|
|
185
|
+
return last;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function waitForStop(pid: number, timeoutMs: number): Promise<void> {
|
|
189
|
+
const deadline = Date.now() + timeoutMs;
|
|
190
|
+
while (Date.now() < deadline) {
|
|
191
|
+
if (!isPidAlive(pid)) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
await sleep(100);
|
|
195
|
+
}
|
|
196
|
+
throw new Error(`timed out waiting for eldlock-server pid ${pid} to stop`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function sleep(ms: number): Promise<void> {
|
|
200
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
204
|
+
return error instanceof Error && "code" in error;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function promptHidden(prompt: string): Promise<string> {
|
|
208
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
209
|
+
throw new Error("YubiKey PIN is required, but this terminal is not interactive");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
const stdin = process.stdin;
|
|
214
|
+
const wasRaw = stdin.isRaw;
|
|
215
|
+
let value = "";
|
|
216
|
+
|
|
217
|
+
const cleanup = (): void => {
|
|
218
|
+
stdin.off("data", onData);
|
|
219
|
+
stdin.setRawMode(wasRaw);
|
|
220
|
+
stdin.pause();
|
|
221
|
+
process.stdout.write("\n");
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const onData = (chunk: Buffer): void => {
|
|
225
|
+
const text = chunk.toString("utf8");
|
|
226
|
+
for (const char of text) {
|
|
227
|
+
if (char === "\u0003") {
|
|
228
|
+
cleanup();
|
|
229
|
+
reject(new Error("cancelled"));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (char === "\r" || char === "\n") {
|
|
233
|
+
cleanup();
|
|
234
|
+
resolve(value);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (char === "\u007f" || char === "\b") {
|
|
238
|
+
value = value.slice(0, -1);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
value += char;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
process.stdout.write(prompt);
|
|
246
|
+
stdin.setRawMode(true);
|
|
247
|
+
stdin.resume();
|
|
248
|
+
stdin.on("data", onData);
|
|
249
|
+
});
|
|
250
|
+
}
|