@keywaysh/cli 0.1.0 → 0.1.1
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/dist/auth-QLPQ24HZ.js +12 -0
- package/dist/chunk-F4C46224.js +102 -0
- package/dist/cli.js +34 -108
- package/package.json +1 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// src/utils/auth.ts
|
|
2
|
+
import Conf from "conf";
|
|
3
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
var store = new Conf({
|
|
8
|
+
projectName: "keyway",
|
|
9
|
+
configName: "config",
|
|
10
|
+
fileMode: 384
|
|
11
|
+
});
|
|
12
|
+
var KEY_DIR = join(homedir(), ".keyway");
|
|
13
|
+
var KEY_FILE = join(KEY_DIR, ".key");
|
|
14
|
+
function getOrCreateEncryptionKey() {
|
|
15
|
+
if (!existsSync(KEY_DIR)) {
|
|
16
|
+
mkdirSync(KEY_DIR, { recursive: true, mode: 448 });
|
|
17
|
+
}
|
|
18
|
+
if (existsSync(KEY_FILE)) {
|
|
19
|
+
const keyHex2 = readFileSync(KEY_FILE, "utf-8").trim();
|
|
20
|
+
if (keyHex2.length === 64) {
|
|
21
|
+
return Buffer.from(keyHex2, "hex");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const key = randomBytes(32);
|
|
25
|
+
const keyHex = key.toString("hex");
|
|
26
|
+
writeFileSync(KEY_FILE, keyHex, { mode: 384 });
|
|
27
|
+
try {
|
|
28
|
+
chmodSync(KEY_FILE, 384);
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
return key;
|
|
32
|
+
}
|
|
33
|
+
function encryptToken(token) {
|
|
34
|
+
const key = getOrCreateEncryptionKey();
|
|
35
|
+
const iv = randomBytes(16);
|
|
36
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
37
|
+
const encrypted = Buffer.concat([cipher.update(token, "utf8"), cipher.final()]);
|
|
38
|
+
const authTag = cipher.getAuthTag();
|
|
39
|
+
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`;
|
|
40
|
+
}
|
|
41
|
+
function decryptToken(encryptedData) {
|
|
42
|
+
const key = getOrCreateEncryptionKey();
|
|
43
|
+
const parts = encryptedData.split(":");
|
|
44
|
+
if (parts.length !== 3) {
|
|
45
|
+
throw new Error("Invalid encrypted token format");
|
|
46
|
+
}
|
|
47
|
+
const iv = Buffer.from(parts[0], "hex");
|
|
48
|
+
const authTag = Buffer.from(parts[1], "hex");
|
|
49
|
+
const encrypted = Buffer.from(parts[2], "hex");
|
|
50
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
51
|
+
decipher.setAuthTag(authTag);
|
|
52
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
53
|
+
return decrypted.toString("utf8");
|
|
54
|
+
}
|
|
55
|
+
function isExpired(auth) {
|
|
56
|
+
if (!auth.expiresAt) return false;
|
|
57
|
+
const expires = Date.parse(auth.expiresAt);
|
|
58
|
+
if (Number.isNaN(expires)) return false;
|
|
59
|
+
return expires <= Date.now();
|
|
60
|
+
}
|
|
61
|
+
async function getStoredAuth() {
|
|
62
|
+
const encryptedData = store.get("auth");
|
|
63
|
+
if (!encryptedData) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const decrypted = decryptToken(encryptedData);
|
|
68
|
+
const auth = JSON.parse(decrypted);
|
|
69
|
+
if (isExpired(auth)) {
|
|
70
|
+
clearAuth();
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return auth;
|
|
74
|
+
} catch {
|
|
75
|
+
console.error("Failed to decrypt stored auth, clearing...");
|
|
76
|
+
clearAuth();
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function saveAuthToken(token, meta) {
|
|
81
|
+
const auth = {
|
|
82
|
+
keywayToken: token,
|
|
83
|
+
githubLogin: meta?.githubLogin,
|
|
84
|
+
expiresAt: meta?.expiresAt,
|
|
85
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
86
|
+
};
|
|
87
|
+
const encrypted = encryptToken(JSON.stringify(auth));
|
|
88
|
+
store.set("auth", encrypted);
|
|
89
|
+
}
|
|
90
|
+
function clearAuth() {
|
|
91
|
+
store.delete("auth");
|
|
92
|
+
}
|
|
93
|
+
function getAuthFilePath() {
|
|
94
|
+
return store.path;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export {
|
|
98
|
+
getStoredAuth,
|
|
99
|
+
saveAuthToken,
|
|
100
|
+
clearAuth,
|
|
101
|
+
getAuthFilePath
|
|
102
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
clearAuth,
|
|
4
|
+
getAuthFilePath,
|
|
5
|
+
getStoredAuth,
|
|
6
|
+
saveAuthToken
|
|
7
|
+
} from "./chunk-F4C46224.js";
|
|
2
8
|
|
|
3
9
|
// src/cli.ts
|
|
4
10
|
import { Command } from "commander";
|
|
@@ -70,7 +76,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
|
70
76
|
// package.json
|
|
71
77
|
var package_default = {
|
|
72
78
|
name: "@keywaysh/cli",
|
|
73
|
-
version: "0.1.
|
|
79
|
+
version: "0.1.1",
|
|
74
80
|
description: "One link to all your secrets",
|
|
75
81
|
type: "module",
|
|
76
82
|
bin: {
|
|
@@ -701,104 +707,6 @@ import pc2 from "picocolors";
|
|
|
701
707
|
import readline from "readline";
|
|
702
708
|
import open from "open";
|
|
703
709
|
import prompts2 from "prompts";
|
|
704
|
-
|
|
705
|
-
// src/utils/auth.ts
|
|
706
|
-
import Conf from "conf";
|
|
707
|
-
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
708
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
|
|
709
|
-
import { join } from "path";
|
|
710
|
-
import { homedir } from "os";
|
|
711
|
-
var store = new Conf({
|
|
712
|
-
projectName: "keyway",
|
|
713
|
-
configName: "config",
|
|
714
|
-
fileMode: 384
|
|
715
|
-
});
|
|
716
|
-
var KEY_DIR = join(homedir(), ".keyway");
|
|
717
|
-
var KEY_FILE = join(KEY_DIR, ".key");
|
|
718
|
-
function getOrCreateEncryptionKey() {
|
|
719
|
-
if (!existsSync(KEY_DIR)) {
|
|
720
|
-
mkdirSync(KEY_DIR, { recursive: true, mode: 448 });
|
|
721
|
-
}
|
|
722
|
-
if (existsSync(KEY_FILE)) {
|
|
723
|
-
const keyHex2 = readFileSync(KEY_FILE, "utf-8").trim();
|
|
724
|
-
if (keyHex2.length === 64) {
|
|
725
|
-
return Buffer.from(keyHex2, "hex");
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
const key = randomBytes(32);
|
|
729
|
-
const keyHex = key.toString("hex");
|
|
730
|
-
writeFileSync(KEY_FILE, keyHex, { mode: 384 });
|
|
731
|
-
try {
|
|
732
|
-
chmodSync(KEY_FILE, 384);
|
|
733
|
-
} catch {
|
|
734
|
-
}
|
|
735
|
-
return key;
|
|
736
|
-
}
|
|
737
|
-
function encryptToken(token) {
|
|
738
|
-
const key = getOrCreateEncryptionKey();
|
|
739
|
-
const iv = randomBytes(16);
|
|
740
|
-
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
741
|
-
const encrypted = Buffer.concat([cipher.update(token, "utf8"), cipher.final()]);
|
|
742
|
-
const authTag = cipher.getAuthTag();
|
|
743
|
-
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`;
|
|
744
|
-
}
|
|
745
|
-
function decryptToken(encryptedData) {
|
|
746
|
-
const key = getOrCreateEncryptionKey();
|
|
747
|
-
const parts = encryptedData.split(":");
|
|
748
|
-
if (parts.length !== 3) {
|
|
749
|
-
throw new Error("Invalid encrypted token format");
|
|
750
|
-
}
|
|
751
|
-
const iv = Buffer.from(parts[0], "hex");
|
|
752
|
-
const authTag = Buffer.from(parts[1], "hex");
|
|
753
|
-
const encrypted = Buffer.from(parts[2], "hex");
|
|
754
|
-
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
755
|
-
decipher.setAuthTag(authTag);
|
|
756
|
-
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
757
|
-
return decrypted.toString("utf8");
|
|
758
|
-
}
|
|
759
|
-
function isExpired(auth) {
|
|
760
|
-
if (!auth.expiresAt) return false;
|
|
761
|
-
const expires = Date.parse(auth.expiresAt);
|
|
762
|
-
if (Number.isNaN(expires)) return false;
|
|
763
|
-
return expires <= Date.now();
|
|
764
|
-
}
|
|
765
|
-
async function getStoredAuth() {
|
|
766
|
-
const encryptedData = store.get("auth");
|
|
767
|
-
if (!encryptedData) {
|
|
768
|
-
return null;
|
|
769
|
-
}
|
|
770
|
-
try {
|
|
771
|
-
const decrypted = decryptToken(encryptedData);
|
|
772
|
-
const auth = JSON.parse(decrypted);
|
|
773
|
-
if (isExpired(auth)) {
|
|
774
|
-
clearAuth();
|
|
775
|
-
return null;
|
|
776
|
-
}
|
|
777
|
-
return auth;
|
|
778
|
-
} catch {
|
|
779
|
-
console.error("Failed to decrypt stored auth, clearing...");
|
|
780
|
-
clearAuth();
|
|
781
|
-
return null;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
async function saveAuthToken(token, meta) {
|
|
785
|
-
const auth = {
|
|
786
|
-
keywayToken: token,
|
|
787
|
-
githubLogin: meta?.githubLogin,
|
|
788
|
-
expiresAt: meta?.expiresAt,
|
|
789
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
790
|
-
};
|
|
791
|
-
const encrypted = encryptToken(JSON.stringify(auth));
|
|
792
|
-
store.set("auth", encrypted);
|
|
793
|
-
}
|
|
794
|
-
function clearAuth() {
|
|
795
|
-
store.delete("auth");
|
|
796
|
-
}
|
|
797
|
-
function getAuthFilePath() {
|
|
798
|
-
return store.path;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// src/cmds/login.ts
|
|
802
710
|
function sleep(ms) {
|
|
803
711
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
804
712
|
}
|
|
@@ -1213,11 +1121,18 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1213
1121
|
const [repoOwner, repoName] = repoFullName.split("/");
|
|
1214
1122
|
const envToken = process.env.KEYWAY_TOKEN;
|
|
1215
1123
|
if (envToken) {
|
|
1216
|
-
|
|
1124
|
+
const result = await ensureGitHubAppInstalledOnly(repoFullName, envToken);
|
|
1125
|
+
if (result === null) {
|
|
1126
|
+
throw new Error("KEYWAY_TOKEN is invalid or expired. Please update the token.");
|
|
1127
|
+
}
|
|
1128
|
+
return result;
|
|
1217
1129
|
}
|
|
1218
1130
|
const stored = await getStoredAuth();
|
|
1219
1131
|
if (stored?.keywayToken) {
|
|
1220
|
-
|
|
1132
|
+
const result = await ensureGitHubAppInstalledOnly(repoFullName, stored.keywayToken);
|
|
1133
|
+
if (result !== null) {
|
|
1134
|
+
return result;
|
|
1135
|
+
}
|
|
1221
1136
|
}
|
|
1222
1137
|
const allowPrompt = options.allowPrompt !== false;
|
|
1223
1138
|
if (!allowPrompt || !isInteractive2()) {
|
|
@@ -1285,7 +1200,18 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1285
1200
|
}
|
|
1286
1201
|
async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
1287
1202
|
const [repoOwner, repoName] = repoFullName.split("/");
|
|
1288
|
-
|
|
1203
|
+
let status;
|
|
1204
|
+
try {
|
|
1205
|
+
status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
if (error instanceof APIError && error.statusCode === 401) {
|
|
1208
|
+
console.log(pc4.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
|
|
1209
|
+
const { clearAuth: clearAuth2 } = await import("./auth-QLPQ24HZ.js");
|
|
1210
|
+
clearAuth2();
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
throw error;
|
|
1214
|
+
}
|
|
1289
1215
|
if (status.installed) {
|
|
1290
1216
|
return accessToken;
|
|
1291
1217
|
}
|
|
@@ -1500,9 +1426,9 @@ import pc6 from "picocolors";
|
|
|
1500
1426
|
|
|
1501
1427
|
// src/core/doctor.ts
|
|
1502
1428
|
import { execSync as execSync2 } from "child_process";
|
|
1503
|
-
import { writeFileSync
|
|
1429
|
+
import { writeFileSync, unlinkSync, readFileSync, existsSync } from "fs";
|
|
1504
1430
|
import { tmpdir } from "os";
|
|
1505
|
-
import { join
|
|
1431
|
+
import { join } from "path";
|
|
1506
1432
|
var API_HEALTH_URL = `${process.env.KEYWAY_API_URL || INTERNAL_API_URL}/v1/health`;
|
|
1507
1433
|
async function checkNode() {
|
|
1508
1434
|
const nodeVersion = process.versions.node;
|
|
@@ -1619,9 +1545,9 @@ async function checkNetwork() {
|
|
|
1619
1545
|
}
|
|
1620
1546
|
}
|
|
1621
1547
|
async function checkFileSystem() {
|
|
1622
|
-
const testFile =
|
|
1548
|
+
const testFile = join(tmpdir(), `.keyway-test-${Date.now()}.tmp`);
|
|
1623
1549
|
try {
|
|
1624
|
-
|
|
1550
|
+
writeFileSync(testFile, "test");
|
|
1625
1551
|
unlinkSync(testFile);
|
|
1626
1552
|
return {
|
|
1627
1553
|
id: "filesystem",
|
|
@@ -1640,7 +1566,7 @@ async function checkFileSystem() {
|
|
|
1640
1566
|
}
|
|
1641
1567
|
async function checkGitignore() {
|
|
1642
1568
|
try {
|
|
1643
|
-
if (!
|
|
1569
|
+
if (!existsSync(".gitignore")) {
|
|
1644
1570
|
return {
|
|
1645
1571
|
id: "gitignore",
|
|
1646
1572
|
name: ".gitignore configuration",
|
|
@@ -1648,7 +1574,7 @@ async function checkGitignore() {
|
|
|
1648
1574
|
detail: "No .gitignore file found"
|
|
1649
1575
|
};
|
|
1650
1576
|
}
|
|
1651
|
-
const gitignoreContent =
|
|
1577
|
+
const gitignoreContent = readFileSync(".gitignore", "utf-8");
|
|
1652
1578
|
const hasEnvPattern = gitignoreContent.includes("*.env") || gitignoreContent.includes(".env*");
|
|
1653
1579
|
const hasDotEnv = gitignoreContent.includes(".env");
|
|
1654
1580
|
if (hasEnvPattern || hasDotEnv) {
|