learn-secrets-sdk 1.5.0 → 1.6.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/cli/index.mjs +228 -46
- package/dist/index.d.mts +52 -5
- package/dist/index.d.ts +52 -5
- package/dist/index.global.js +9 -5
- package/dist/index.mjs +9 -5
- package/package.json +2 -2
package/dist/cli/index.mjs
CHANGED
|
@@ -30,28 +30,45 @@ function ensureLearnDir() {
|
|
|
30
30
|
fs.mkdirSync(learnDir, { recursive: true, mode: 448 });
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
function
|
|
33
|
+
function readAllCredentials() {
|
|
34
34
|
try {
|
|
35
35
|
const credPath = getCredentialsPath();
|
|
36
36
|
if (!fs.existsSync(credPath)) {
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
39
|
const data = fs.readFileSync(credPath, "utf-8");
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
40
|
+
const store = JSON.parse(data);
|
|
41
|
+
if (store.access_token && !store["learn-secrets-sdk"] && !store["learn-auth-sdk"]) {
|
|
42
|
+
return {
|
|
43
|
+
"learn-secrets-sdk": store
|
|
44
|
+
};
|
|
43
45
|
}
|
|
44
|
-
return
|
|
46
|
+
return store;
|
|
45
47
|
} catch (err) {
|
|
46
48
|
return null;
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
|
-
function
|
|
51
|
+
function writeAllCredentials(store) {
|
|
50
52
|
ensureLearnDir();
|
|
51
53
|
const credPath = getCredentialsPath();
|
|
52
|
-
const data = JSON.stringify(
|
|
54
|
+
const data = JSON.stringify(store, null, 2);
|
|
53
55
|
fs.writeFileSync(credPath, data, { mode: 384 });
|
|
54
56
|
}
|
|
57
|
+
function readSDKCredentials(sdkName) {
|
|
58
|
+
const store = readAllCredentials();
|
|
59
|
+
if (!store) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return store[sdkName] || null;
|
|
63
|
+
}
|
|
64
|
+
function writeSDKCredentials(sdkName, creds) {
|
|
65
|
+
const store = readAllCredentials() || {};
|
|
66
|
+
store[sdkName] = creds;
|
|
67
|
+
writeAllCredentials(store);
|
|
68
|
+
}
|
|
69
|
+
function readCredentials() {
|
|
70
|
+
return readSDKCredentials("learn-secrets-sdk");
|
|
71
|
+
}
|
|
55
72
|
function deleteCredentials() {
|
|
56
73
|
try {
|
|
57
74
|
const credPath = getCredentialsPath();
|
|
@@ -66,11 +83,94 @@ function hasValidCredentials() {
|
|
|
66
83
|
if (!creds) {
|
|
67
84
|
return false;
|
|
68
85
|
}
|
|
86
|
+
if (!creds.expires_at) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
69
89
|
const expiresAt = new Date(creds.expires_at);
|
|
70
90
|
const now = /* @__PURE__ */ new Date();
|
|
71
91
|
return expiresAt > now;
|
|
72
92
|
}
|
|
73
93
|
|
|
94
|
+
// src/machine-id.ts
|
|
95
|
+
import { exec } from "child_process";
|
|
96
|
+
import { promisify } from "util";
|
|
97
|
+
import { createHash } from "crypto";
|
|
98
|
+
import * as os2 from "os";
|
|
99
|
+
var execAsync = promisify(exec);
|
|
100
|
+
async function getMachineId() {
|
|
101
|
+
const platform2 = process.platform;
|
|
102
|
+
try {
|
|
103
|
+
let rawId;
|
|
104
|
+
if (platform2 === "darwin") {
|
|
105
|
+
rawId = await getMacOSId();
|
|
106
|
+
} else if (platform2 === "linux") {
|
|
107
|
+
rawId = await getLinuxId();
|
|
108
|
+
} else if (platform2 === "win32") {
|
|
109
|
+
rawId = await getWindowsId();
|
|
110
|
+
} else {
|
|
111
|
+
throw new Error(`Unsupported platform: ${platform2}`);
|
|
112
|
+
}
|
|
113
|
+
const machineId = createHash("sha256").update(rawId).digest("hex").substring(0, 32);
|
|
114
|
+
const fingerprintData = JSON.stringify({
|
|
115
|
+
machineId,
|
|
116
|
+
cpus: os2.cpus().length,
|
|
117
|
+
arch: os2.arch(),
|
|
118
|
+
platform: os2.platform(),
|
|
119
|
+
homedir: os2.homedir()
|
|
120
|
+
});
|
|
121
|
+
const fingerprint = createHash("sha256").update(fingerprintData).digest("hex");
|
|
122
|
+
return { machineId, fingerprint };
|
|
123
|
+
} catch (error) {
|
|
124
|
+
throw new Error(`Failed to get machine ID: ${error.message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function getMacOSId() {
|
|
128
|
+
try {
|
|
129
|
+
const { stdout: serial } = await execAsync(
|
|
130
|
+
`ioreg -l | grep IOPlatformSerialNumber | awk '{print $4}' | sed 's/"//g'`
|
|
131
|
+
);
|
|
132
|
+
const { stdout: hostname } = await execAsync("hostname");
|
|
133
|
+
const serialTrimmed = serial.trim();
|
|
134
|
+
const hostnameTrimmed = hostname.trim();
|
|
135
|
+
if (serialTrimmed && hostnameTrimmed) {
|
|
136
|
+
return `${serialTrimmed}-${hostnameTrimmed}`;
|
|
137
|
+
}
|
|
138
|
+
const { stdout: uuid } = await execAsync(
|
|
139
|
+
`ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID | awk '{print $3}' | sed 's/"//g'`
|
|
140
|
+
);
|
|
141
|
+
return uuid.trim();
|
|
142
|
+
} catch (error) {
|
|
143
|
+
throw new Error("Failed to get macOS machine ID");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function getLinuxId() {
|
|
147
|
+
try {
|
|
148
|
+
const { stdout } = await execAsync(
|
|
149
|
+
"cat /etc/machine-id 2>/dev/null || cat /var/lib/dbus/machine-id 2>/dev/null"
|
|
150
|
+
);
|
|
151
|
+
const machineId = stdout.trim();
|
|
152
|
+
if (machineId) {
|
|
153
|
+
return machineId;
|
|
154
|
+
}
|
|
155
|
+
throw new Error("Machine ID file not found");
|
|
156
|
+
} catch (error) {
|
|
157
|
+
throw new Error("Failed to get Linux machine ID");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function getWindowsId() {
|
|
161
|
+
try {
|
|
162
|
+
const { stdout } = await execAsync("wmic csproduct get uuid");
|
|
163
|
+
const lines = stdout.split("\n").map((l) => l.trim());
|
|
164
|
+
const uuid = lines.find((l) => l && l !== "UUID");
|
|
165
|
+
if (uuid) {
|
|
166
|
+
return uuid;
|
|
167
|
+
}
|
|
168
|
+
throw new Error("Failed to parse machine UUID from WMIC");
|
|
169
|
+
} catch (error) {
|
|
170
|
+
throw new Error("Failed to get Windows machine ID");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
74
174
|
// src/management.ts
|
|
75
175
|
var SecretsManagement = class {
|
|
76
176
|
constructor(options) {
|
|
@@ -82,17 +182,17 @@ var SecretsManagement = class {
|
|
|
82
182
|
* Open a URL in the default browser
|
|
83
183
|
*/
|
|
84
184
|
async openBrowser(url) {
|
|
85
|
-
const { exec } = await import("child_process");
|
|
86
|
-
const { promisify } = await import("util");
|
|
87
|
-
const
|
|
88
|
-
const
|
|
185
|
+
const { exec: exec2 } = await import("child_process");
|
|
186
|
+
const { promisify: promisify2 } = await import("util");
|
|
187
|
+
const execAsync2 = promisify2(exec2);
|
|
188
|
+
const platform2 = process.platform;
|
|
89
189
|
try {
|
|
90
|
-
if (
|
|
91
|
-
await
|
|
92
|
-
} else if (
|
|
93
|
-
await
|
|
190
|
+
if (platform2 === "darwin") {
|
|
191
|
+
await execAsync2(`open "${url}"`);
|
|
192
|
+
} else if (platform2 === "win32") {
|
|
193
|
+
await execAsync2(`start "${url}"`);
|
|
94
194
|
} else {
|
|
95
|
-
await
|
|
195
|
+
await execAsync2(`xdg-open "${url}"`);
|
|
96
196
|
}
|
|
97
197
|
} catch (err) {
|
|
98
198
|
console.error("Failed to open browser:", err);
|
|
@@ -106,11 +206,39 @@ var SecretsManagement = class {
|
|
|
106
206
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
107
207
|
}
|
|
108
208
|
/**
|
|
109
|
-
* Browser OAuth login flow
|
|
110
|
-
* Opens browser for user to authorize, then
|
|
209
|
+
* Browser OAuth login flow with 2FA
|
|
210
|
+
* Opens browser for user to authorize, then prompts for 2FA code
|
|
111
211
|
*/
|
|
112
212
|
async login() {
|
|
213
|
+
const SDK_NAME = "learn-secrets-sdk";
|
|
113
214
|
try {
|
|
215
|
+
console.log(`Authenticating with Learn (${SDK_NAME})...
|
|
216
|
+
`);
|
|
217
|
+
const machineInfo = await getMachineId();
|
|
218
|
+
const existingCreds = readSDKCredentials(SDK_NAME);
|
|
219
|
+
if (existingCreds && existingCreds.access_token) {
|
|
220
|
+
const checkResponse = await fetch(`${this.baseUrl}/api/auth/cli/check-authorization`, {
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers: {
|
|
223
|
+
"Content-Type": "application/json"
|
|
224
|
+
},
|
|
225
|
+
body: JSON.stringify({
|
|
226
|
+
machine_id: machineInfo.machineId,
|
|
227
|
+
sdk_name: SDK_NAME
|
|
228
|
+
})
|
|
229
|
+
});
|
|
230
|
+
if (checkResponse.ok) {
|
|
231
|
+
const checkData = await checkResponse.json();
|
|
232
|
+
if (checkData.authorized) {
|
|
233
|
+
console.log("This machine is already authorized.");
|
|
234
|
+
console.log(`Logged in as: ${checkData.user_email || checkData.user_id}`);
|
|
235
|
+
return {
|
|
236
|
+
success: true,
|
|
237
|
+
user: checkData.user_email || checkData.user_id
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
114
242
|
const deviceResponse = await fetch(`${this.baseUrl}/api/auth/cli/device`, {
|
|
115
243
|
method: "POST",
|
|
116
244
|
headers: {
|
|
@@ -124,12 +252,13 @@ var SecretsManagement = class {
|
|
|
124
252
|
);
|
|
125
253
|
}
|
|
126
254
|
const deviceData = await deviceResponse.json();
|
|
127
|
-
console.log("\nTo authorize this
|
|
128
|
-
console.log(`
|
|
129
|
-
console.log(
|
|
130
|
-
console.log(`
|
|
255
|
+
console.log("\nTo authorize this device:");
|
|
256
|
+
console.log(`1. Visit: ${deviceData.verification_uri}`);
|
|
257
|
+
console.log(`2. Enter code: ${deviceData.user_code}`);
|
|
258
|
+
console.log(`3. Complete 2FA verification`);
|
|
131
259
|
console.log("\nOpening browser...\n");
|
|
132
260
|
await this.openBrowser(deviceData.verification_uri_complete);
|
|
261
|
+
let deviceAuthorized = false;
|
|
133
262
|
const interval = deviceData.interval * 1e3;
|
|
134
263
|
const maxAttempts = Math.ceil(deviceData.expires_in / deviceData.interval);
|
|
135
264
|
let attempts = 0;
|
|
@@ -149,28 +278,79 @@ var SecretsManagement = class {
|
|
|
149
278
|
process.stdout.write(".");
|
|
150
279
|
continue;
|
|
151
280
|
}
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
errorData.message || "Failed to exchange device code",
|
|
156
|
-
tokenResponse.status
|
|
157
|
-
);
|
|
281
|
+
if (tokenResponse.ok) {
|
|
282
|
+
deviceAuthorized = true;
|
|
283
|
+
break;
|
|
158
284
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
285
|
+
}
|
|
286
|
+
if (!deviceAuthorized) {
|
|
287
|
+
throw new SecretsSDKError("Device authorization timeout", 408);
|
|
288
|
+
}
|
|
289
|
+
console.log("\n\nDevice authorized! Sending 2FA code...");
|
|
290
|
+
const tfaResponse = await fetch(`${this.baseUrl}/api/auth/2fa/send`, {
|
|
291
|
+
method: "POST",
|
|
292
|
+
headers: {
|
|
293
|
+
"Content-Type": "application/json"
|
|
294
|
+
},
|
|
295
|
+
body: JSON.stringify({
|
|
296
|
+
device_code: deviceData.device_code,
|
|
297
|
+
sdk_name: SDK_NAME,
|
|
298
|
+
machine_id: machineInfo.machineId
|
|
299
|
+
})
|
|
300
|
+
});
|
|
301
|
+
if (!tfaResponse.ok) {
|
|
302
|
+
const errorData = await tfaResponse.json().catch(() => ({}));
|
|
303
|
+
throw new SecretsSDKError(
|
|
304
|
+
errorData.message || "Failed to send 2FA code",
|
|
305
|
+
tfaResponse.status
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
console.log("2FA code sent to your email.\n");
|
|
309
|
+
const readline = await import("readline");
|
|
310
|
+
const rl = readline.createInterface({
|
|
311
|
+
input: process.stdin,
|
|
312
|
+
output: process.stdout
|
|
313
|
+
});
|
|
314
|
+
const tfaCode = await new Promise((resolve2) => {
|
|
315
|
+
rl.question("Enter 2FA code from email: ", (answer) => {
|
|
316
|
+
rl.close();
|
|
317
|
+
resolve2(answer);
|
|
166
318
|
});
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
319
|
+
});
|
|
320
|
+
const verifyResponse = await fetch(`${this.baseUrl}/api/auth/2fa/verify`, {
|
|
321
|
+
method: "POST",
|
|
322
|
+
headers: {
|
|
323
|
+
"Content-Type": "application/json"
|
|
324
|
+
},
|
|
325
|
+
body: JSON.stringify({
|
|
326
|
+
device_code: deviceData.device_code,
|
|
327
|
+
code: tfaCode.trim()
|
|
328
|
+
})
|
|
329
|
+
});
|
|
330
|
+
if (!verifyResponse.ok) {
|
|
331
|
+
const errorData = await verifyResponse.json().catch(() => ({}));
|
|
332
|
+
throw new SecretsSDKError(
|
|
333
|
+
errorData.message || "2FA verification failed",
|
|
334
|
+
verifyResponse.status
|
|
335
|
+
);
|
|
172
336
|
}
|
|
173
|
-
|
|
337
|
+
const tokenData = await verifyResponse.json();
|
|
338
|
+
writeSDKCredentials(SDK_NAME, {
|
|
339
|
+
access_token: tokenData.access_token,
|
|
340
|
+
refresh_token: null,
|
|
341
|
+
expires_at: null,
|
|
342
|
+
user_id: tokenData.user_id,
|
|
343
|
+
machine_id: machineInfo.machineId,
|
|
344
|
+
sdk_name: SDK_NAME,
|
|
345
|
+
authorized_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
346
|
+
});
|
|
347
|
+
console.log("\nAuthentication successful!");
|
|
348
|
+
console.log(`Logged in as: ${tokenData.user_email || tokenData.user_id}`);
|
|
349
|
+
console.log("This machine is now permanently authorized.\n");
|
|
350
|
+
return {
|
|
351
|
+
success: true,
|
|
352
|
+
user: tokenData.user_email || tokenData.user_id
|
|
353
|
+
};
|
|
174
354
|
} catch (err) {
|
|
175
355
|
if (err instanceof SecretsSDKError) {
|
|
176
356
|
throw err;
|
|
@@ -203,10 +383,12 @@ var SecretsManagement = class {
|
|
|
203
383
|
if (!creds) {
|
|
204
384
|
throw new SecretsSDKError("Not authenticated. Run login() first.", 401);
|
|
205
385
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
386
|
+
if (creds.expires_at) {
|
|
387
|
+
const expiresAt = new Date(creds.expires_at);
|
|
388
|
+
const now = /* @__PURE__ */ new Date();
|
|
389
|
+
if (expiresAt <= now) {
|
|
390
|
+
throw new SecretsSDKError("Token expired. Run login() again.", 401);
|
|
391
|
+
}
|
|
210
392
|
}
|
|
211
393
|
return creds.access_token;
|
|
212
394
|
}
|
|
@@ -816,7 +998,7 @@ async function deployCommand() {
|
|
|
816
998
|
|
|
817
999
|
// src/cli/index.ts
|
|
818
1000
|
var program = new Command();
|
|
819
|
-
program.name("learn-secrets").description("CLI tool for managing API secrets in static sites").version("1.
|
|
1001
|
+
program.name("learn-secrets").description("CLI tool for managing API secrets in static sites").version("1.6.0");
|
|
820
1002
|
program.command("setup").description("Complete setup: create project, generate token, upload secrets").requiredOption("-e, --email <email>", "Your Learn account email").requiredOption("-p, --password <password>", "Your Learn account password").requiredOption("-o, --origin <domain>", "Your site origin (e.g., mysite.com)").option("--project-name <name>", 'Project name (default: "Project - <origin>")').option("--env <file>", "Path to .env file (default: .env)", ".env").option("--base-url <url>", "Custom base URL for API").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
|
|
821
1003
|
await setupCommand(options);
|
|
822
1004
|
});
|
package/dist/index.d.mts
CHANGED
|
@@ -102,10 +102,18 @@ interface SyncResult {
|
|
|
102
102
|
}
|
|
103
103
|
interface CLICredentials {
|
|
104
104
|
access_token: string;
|
|
105
|
-
refresh_token: string;
|
|
106
|
-
expires_at: string;
|
|
105
|
+
refresh_token: string | null;
|
|
106
|
+
expires_at: string | null;
|
|
107
107
|
user_id: string;
|
|
108
|
+
machine_id?: string;
|
|
109
|
+
sdk_name?: string;
|
|
110
|
+
authorized_at?: string;
|
|
108
111
|
}
|
|
112
|
+
interface SDKCredentialsStore {
|
|
113
|
+
'learn-secrets-sdk'?: CLICredentials;
|
|
114
|
+
'learn-auth-sdk'?: CLICredentials;
|
|
115
|
+
}
|
|
116
|
+
type SDKName = 'learn-secrets-sdk' | 'learn-auth-sdk';
|
|
109
117
|
|
|
110
118
|
declare class SecretsSDK {
|
|
111
119
|
private appId;
|
|
@@ -278,8 +286,8 @@ declare class SecretsManagement {
|
|
|
278
286
|
*/
|
|
279
287
|
private sleep;
|
|
280
288
|
/**
|
|
281
|
-
* Browser OAuth login flow
|
|
282
|
-
* Opens browser for user to authorize, then
|
|
289
|
+
* Browser OAuth login flow with 2FA
|
|
290
|
+
* Opens browser for user to authorize, then prompts for 2FA code
|
|
283
291
|
*/
|
|
284
292
|
login(): Promise<{
|
|
285
293
|
success: boolean;
|
|
@@ -382,4 +390,43 @@ declare function loadSecretsConfig(configPath: string): {
|
|
|
382
390
|
secrets: SecretConfig[];
|
|
383
391
|
};
|
|
384
392
|
|
|
385
|
-
|
|
393
|
+
/**
|
|
394
|
+
* Machine ID Generation
|
|
395
|
+
* Cross-platform machine identification for CLI authentication
|
|
396
|
+
*/
|
|
397
|
+
interface MachineInfo {
|
|
398
|
+
machineId: string;
|
|
399
|
+
fingerprint: string;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Get a unique machine identifier
|
|
403
|
+
* This ID persists across reboots and SDK reinstalls
|
|
404
|
+
*/
|
|
405
|
+
declare function getMachineId(): Promise<MachineInfo>;
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Read all credentials from file (SDK-keyed format)
|
|
409
|
+
*/
|
|
410
|
+
declare function readAllCredentials(): SDKCredentialsStore | null;
|
|
411
|
+
/**
|
|
412
|
+
* Write all credentials to file (SDK-keyed format)
|
|
413
|
+
*/
|
|
414
|
+
declare function writeAllCredentials(store: SDKCredentialsStore): void;
|
|
415
|
+
/**
|
|
416
|
+
* Read credentials for a specific SDK
|
|
417
|
+
*/
|
|
418
|
+
declare function readSDKCredentials(sdkName: SDKName): CLICredentials | null;
|
|
419
|
+
/**
|
|
420
|
+
* Write credentials for a specific SDK
|
|
421
|
+
*/
|
|
422
|
+
declare function writeSDKCredentials(sdkName: SDKName, creds: CLICredentials): void;
|
|
423
|
+
/**
|
|
424
|
+
* Delete credentials for a specific SDK
|
|
425
|
+
*/
|
|
426
|
+
declare function deleteSDKCredentials(sdkName: SDKName): void;
|
|
427
|
+
/**
|
|
428
|
+
* Check if credentials exist for a specific SDK
|
|
429
|
+
*/
|
|
430
|
+
declare function hasSDKCredentials(sdkName: SDKName): boolean;
|
|
431
|
+
|
|
432
|
+
export { type CLICredentials, InvalidTokenError, type MachineInfo, type MaskedSecret, OriginMismatchError, type ProxyRequest, type ProxyResponse, RateLimitError, type RateLimitInfo, type SDKCredentialsStore, type SDKName, type SecretConfig, SecretsManagement, type SecretsManagementOptions, SecretsSDK, SecretsSDKError, type SecretsSDKOptions, type SyncOptions, type SyncResult, deleteSDKCredentials, getMachineId, hasSDKCredentials, loadSecretsConfig, readAllCredentials, readSDKCredentials, resolveEnvInSecret, resolveEnvInSecrets, resolveEnvString, writeAllCredentials, writeSDKCredentials };
|
package/dist/index.d.ts
CHANGED
|
@@ -102,10 +102,18 @@ interface SyncResult {
|
|
|
102
102
|
}
|
|
103
103
|
interface CLICredentials {
|
|
104
104
|
access_token: string;
|
|
105
|
-
refresh_token: string;
|
|
106
|
-
expires_at: string;
|
|
105
|
+
refresh_token: string | null;
|
|
106
|
+
expires_at: string | null;
|
|
107
107
|
user_id: string;
|
|
108
|
+
machine_id?: string;
|
|
109
|
+
sdk_name?: string;
|
|
110
|
+
authorized_at?: string;
|
|
108
111
|
}
|
|
112
|
+
interface SDKCredentialsStore {
|
|
113
|
+
'learn-secrets-sdk'?: CLICredentials;
|
|
114
|
+
'learn-auth-sdk'?: CLICredentials;
|
|
115
|
+
}
|
|
116
|
+
type SDKName = 'learn-secrets-sdk' | 'learn-auth-sdk';
|
|
109
117
|
|
|
110
118
|
declare class SecretsSDK {
|
|
111
119
|
private appId;
|
|
@@ -278,8 +286,8 @@ declare class SecretsManagement {
|
|
|
278
286
|
*/
|
|
279
287
|
private sleep;
|
|
280
288
|
/**
|
|
281
|
-
* Browser OAuth login flow
|
|
282
|
-
* Opens browser for user to authorize, then
|
|
289
|
+
* Browser OAuth login flow with 2FA
|
|
290
|
+
* Opens browser for user to authorize, then prompts for 2FA code
|
|
283
291
|
*/
|
|
284
292
|
login(): Promise<{
|
|
285
293
|
success: boolean;
|
|
@@ -382,4 +390,43 @@ declare function loadSecretsConfig(configPath: string): {
|
|
|
382
390
|
secrets: SecretConfig[];
|
|
383
391
|
};
|
|
384
392
|
|
|
385
|
-
|
|
393
|
+
/**
|
|
394
|
+
* Machine ID Generation
|
|
395
|
+
* Cross-platform machine identification for CLI authentication
|
|
396
|
+
*/
|
|
397
|
+
interface MachineInfo {
|
|
398
|
+
machineId: string;
|
|
399
|
+
fingerprint: string;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Get a unique machine identifier
|
|
403
|
+
* This ID persists across reboots and SDK reinstalls
|
|
404
|
+
*/
|
|
405
|
+
declare function getMachineId(): Promise<MachineInfo>;
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Read all credentials from file (SDK-keyed format)
|
|
409
|
+
*/
|
|
410
|
+
declare function readAllCredentials(): SDKCredentialsStore | null;
|
|
411
|
+
/**
|
|
412
|
+
* Write all credentials to file (SDK-keyed format)
|
|
413
|
+
*/
|
|
414
|
+
declare function writeAllCredentials(store: SDKCredentialsStore): void;
|
|
415
|
+
/**
|
|
416
|
+
* Read credentials for a specific SDK
|
|
417
|
+
*/
|
|
418
|
+
declare function readSDKCredentials(sdkName: SDKName): CLICredentials | null;
|
|
419
|
+
/**
|
|
420
|
+
* Write credentials for a specific SDK
|
|
421
|
+
*/
|
|
422
|
+
declare function writeSDKCredentials(sdkName: SDKName, creds: CLICredentials): void;
|
|
423
|
+
/**
|
|
424
|
+
* Delete credentials for a specific SDK
|
|
425
|
+
*/
|
|
426
|
+
declare function deleteSDKCredentials(sdkName: SDKName): void;
|
|
427
|
+
/**
|
|
428
|
+
* Check if credentials exist for a specific SDK
|
|
429
|
+
*/
|
|
430
|
+
declare function hasSDKCredentials(sdkName: SDKName): boolean;
|
|
431
|
+
|
|
432
|
+
export { type CLICredentials, InvalidTokenError, type MachineInfo, type MaskedSecret, OriginMismatchError, type ProxyRequest, type ProxyResponse, RateLimitError, type RateLimitInfo, type SDKCredentialsStore, type SDKName, type SecretConfig, SecretsManagement, type SecretsManagementOptions, SecretsSDK, SecretsSDKError, type SecretsSDKOptions, type SyncOptions, type SyncResult, deleteSDKCredentials, getMachineId, hasSDKCredentials, loadSecretsConfig, readAllCredentials, readSDKCredentials, resolveEnvInSecret, resolveEnvInSecrets, resolveEnvString, writeAllCredentials, writeSDKCredentials };
|
package/dist/index.global.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
"use strict";var SecretsSDK=(()=>{var
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"use strict";var SecretsSDK=(()=>{var ee=Object.create;var D=Object.defineProperty;var te=Object.getOwnPropertyDescriptor;var re=Object.getOwnPropertyNames;var se=Object.getPrototypeOf,ne=Object.prototype.hasOwnProperty;var c=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var ie=(t,e)=>()=>(t&&(e=t(t=0)),e);var W=(t,e)=>{for(var r in e)D(t,r,{get:e[r],enumerable:!0})},B=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of re(e))!ne.call(t,n)&&n!==r&&D(t,n,{get:()=>e[n],enumerable:!(s=te(e,n))||s.enumerable});return t};var w=(t,e,r)=>(r=t!=null?ee(se(t)):{},B(e||!t||!t.__esModule?D(r,"default",{value:t,enumerable:!0}):r,t)),oe=t=>B(D({},"__esModule",{value:!0}),t);var Y={};W(Y,{loadSecretsConfig:()=>z,resolveEnvInSecret:()=>L,resolveEnvInSecrets:()=>$,resolveEnvString:()=>m});function m(t){return t.replace(/\$\{([A-Z0-9_]+)\}|\$([A-Z0-9_]+)/g,(e,r,s)=>{let n=r||s,i=process.env[n];if(i===void 0)throw new Error(`Environment variable ${n} is not defined`);return i})}function L(t){let e={name:m(t.name),provider:m(t.provider),api_key:m(t.api_key)};return t.base_url&&(e.base_url=m(t.base_url)),t.auth_header&&(e.auth_header=m(t.auth_header)),t.auth_prefix&&(e.auth_prefix=m(t.auth_prefix)),e}function $(t){return t.map(L)}function z(t){let e=c("fs"),s=c("path").resolve(t);if(!e.existsSync(s))throw new Error(`Config file not found: ${s}`);let n=e.readFileSync(s,"utf-8"),i=JSON.parse(n);if(!i.version)throw new Error('Config file missing "version" field');if(!i.project)throw new Error('Config file missing "project" field');if(!Array.isArray(i.secrets))throw new Error('Config file missing "secrets" array');let a=$(i.secrets);return{version:i.version,project:i.project,secrets:a}}var J=ie(()=>{"use strict"});var ue={};W(ue,{InvalidTokenError:()=>b,OriginMismatchError:()=>v,RateLimitError:()=>x,SecretsManagement:()=>j,SecretsSDK:()=>R,SecretsSDKError:()=>o,deleteSDKCredentials:()=>G,getMachineId:()=>A,hasSDKCredentials:()=>V,loadSecretsConfig:()=>z,readAllCredentials:()=>C,readSDKCredentials:()=>k,resolveEnvInSecret:()=>L,resolveEnvInSecrets:()=>$,resolveEnvString:()=>m,writeAllCredentials:()=>O,writeSDKCredentials:()=>E});var o=class extends Error{constructor(r,s,n){super(r);this.status=s;this.response=n;this.name="SecretsSDKError"}},v=class extends o{constructor(e="Origin not allowed for this token"){super(e,403),this.name="OriginMismatchError"}},x=class extends o{constructor(e="Rate limit exceeded",r=60,s=0,n=100){super(e,429),this.name="RateLimitError",this.retryAfter=r,this.remaining=s,this.limit=n}},b=class extends o{constructor(e="Invalid or expired token"){super(e,401),this.name="InvalidTokenError"}};var R=class{constructor(e={}){this.rateLimitInfo=null;this.appId=e.appId||null,this.token=e.token||e.sessionToken||null,this.baseUrl=e.baseUrl||"https://ctklearn.carsontkempf.workers.dev",this.timeout=e.timeout||3e4,this.retryOn429=e.retryOn429!==void 0?e.retryOn429:!0,this.zeroConfigMode=!this.appId&&!this.token}getUsage(){return this.rateLimitInfo}parseRateLimitHeaders(e){let r=e.get("X-RateLimit-Limit"),s=e.get("X-RateLimit-Remaining"),n=e.get("X-RateLimit-Reset");r&&s&&n&&(this.rateLimitInfo={limit:parseInt(r),remaining:parseInt(s),reset:parseInt(n)})}sleep(e){return new Promise(r=>setTimeout(r,e))}async fetchWithTimeout(e,r,s){let n=new AbortController,i=setTimeout(()=>n.abort(),s);try{return await fetch(e,{...r,signal:n.signal})}finally{clearTimeout(i)}}async call(e,r,s={}){let n=0,i=this.retryOn429?3:0;for(;;)try{let a=this.zeroConfigMode?`${this.baseUrl}/api/sdk/proxy`:`${this.baseUrl}/api/sdk/${this.appId}/proxy`,h={"Content-Type":"application/json"};!this.zeroConfigMode&&this.token&&(h.Authorization=`Bearer ${this.token}`);let u=await this.fetchWithTimeout(a,{method:"POST",headers:h,body:JSON.stringify({keyName:e,endpoint:r,method:s.method||"GET",body:s.body,headers:s.headers})},this.timeout);this.parseRateLimitHeaders(u.headers);let f=await u.json();if(!u.ok){let p=f.data?.message||f?.message||`Request failed with status ${u.status}`;if(u.status===403&&p.includes("Origin"))throw new v(p);if(u.status===401)throw new b(p);if(u.status===429){let H=this.rateLimitInfo?this.rateLimitInfo.reset-Math.floor(Date.now()/1e3):60,I=new x(p,H,this.rateLimitInfo?.remaining||0,this.rateLimitInfo?.limit||100);if(this.retryOn429&&n<i){n++;let M=Math.min(1e3*Math.pow(2,n-1),8e3);await this.sleep(M);continue}throw I}throw new o(p,u.status,f)}return f.data}catch(a){throw a instanceof o?a:a instanceof Error&&a.name==="AbortError"?new o("Request timeout",408):new o(a instanceof Error?a.message:"Unknown error occurred",500)}}async get(e,r,s){return this.call(e,r,{method:"GET",headers:s})}async post(e,r,s,n){return this.call(e,r,{method:"POST",body:s,headers:n})}async put(e,r,s,n){return this.call(e,r,{method:"PUT",body:s,headers:n})}async delete(e,r,s){return this.call(e,r,{method:"DELETE",headers:s})}async patch(e,r,s,n){return this.call(e,r,{method:"PATCH",body:s,headers:n})}setAppId(e){this.appId=e}async secretsRequest(e,r){if(!this.appId)throw new o("App ID required for secrets management. Call setAppId() first.",400);let s=await this.fetchWithTimeout(`${this.baseUrl}/api/projects/${this.appId}/secrets/manage?action=${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:r?JSON.stringify(r):void 0},this.timeout);if(!s.ok){let n=await s.json().catch(()=>({}));throw new o(n.message||`Secrets management failed: ${s.statusText}`,s.status,n)}return s.json()}async listSecrets(){return this.secretsRequest("list")}async createSecret(e){return this.secretsRequest("create",e)}async updateSecret(e){return this.secretsRequest("update",e)}async deleteSecret(e){return this.secretsRequest("delete",{name:e})}async syncEnv(e,r){return this.secretsRequest("sync",{secrets:e,provider:r})}};var d=w(c("fs")),P=w(c("path")),K=w(c("os"));function U(){let t=K.homedir(),e=P.join(t,".learn");return P.join(e,"credentials.json")}function ae(){let t=K.homedir(),e=P.join(t,".learn");d.existsSync(e)||d.mkdirSync(e,{recursive:!0,mode:448})}function C(){try{let t=U();if(!d.existsSync(t))return null;let e=d.readFileSync(t,"utf-8"),r=JSON.parse(e);return r.access_token&&!r["learn-secrets-sdk"]&&!r["learn-auth-sdk"]?{"learn-secrets-sdk":r}:r}catch{return null}}function O(t){ae();let e=U(),r=JSON.stringify(t,null,2);d.writeFileSync(e,r,{mode:384})}function k(t){let e=C();return e&&e[t]||null}function E(t,e){let r=C()||{};r[t]=e,O(r)}function G(t){let e=C();e&&(delete e[t],Object.keys(e).length===0?N():O(e))}function q(){return k("learn-secrets-sdk")}function N(){try{let t=U();d.existsSync(t)&&d.unlinkSync(t)}catch{}}function V(t){let e=k(t);return e!==null&&!!e.access_token}function X(){let t=q();return t?t.expires_at?new Date(t.expires_at)>new Date:!0:!1}var Z=c("child_process"),Q=c("util"),F=c("crypto"),g=w(c("os")),_=(0,Q.promisify)(Z.exec);async function A(){let t=process.platform;try{let e;if(t==="darwin")e=await ce();else if(t==="linux")e=await de();else if(t==="win32")e=await le();else throw new Error(`Unsupported platform: ${t}`);let r=(0,F.createHash)("sha256").update(e).digest("hex").substring(0,32),s=JSON.stringify({machineId:r,cpus:g.cpus().length,arch:g.arch(),platform:g.platform(),homedir:g.homedir()}),n=(0,F.createHash)("sha256").update(s).digest("hex");return{machineId:r,fingerprint:n}}catch(e){throw new Error(`Failed to get machine ID: ${e.message}`)}}async function ce(){try{let{stdout:t}=await _(`ioreg -l | grep IOPlatformSerialNumber | awk '{print $4}' | sed 's/"//g'`),{stdout:e}=await _("hostname"),r=t.trim(),s=e.trim();if(r&&s)return`${r}-${s}`;let{stdout:n}=await _(`ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID | awk '{print $3}' | sed 's/"//g'`);return n.trim()}catch{throw new Error("Failed to get macOS machine ID")}}async function de(){try{let{stdout:t}=await _("cat /etc/machine-id 2>/dev/null || cat /var/lib/dbus/machine-id 2>/dev/null"),e=t.trim();if(e)return e;throw new Error("Machine ID file not found")}catch{throw new Error("Failed to get Linux machine ID")}}async function le(){try{let{stdout:t}=await _("wmic csproduct get uuid"),r=t.split(`
|
|
2
|
+
`).map(s=>s.trim()).find(s=>s&&s!=="UUID");if(r)return r;throw new Error("Failed to parse machine UUID from WMIC")}catch{throw new Error("Failed to get Windows machine ID")}}var j=class{constructor(e){this.appId=e.appId,this.baseUrl=e.baseUrl||"https://ctklearn.carsontkempf.workers.dev",this.timeout=e.timeout||3e4}async openBrowser(e){let{exec:r}=await import("child_process"),{promisify:s}=await import("util"),n=s(r),i=process.platform;try{i==="darwin"?await n(`open "${e}"`):i==="win32"?await n(`start "${e}"`):await n(`xdg-open "${e}"`)}catch(a){throw console.error("Failed to open browser:",a),new o("Failed to open browser",500)}}sleep(e){return new Promise(r=>setTimeout(r,e))}async login(){let e="learn-secrets-sdk";try{console.log(`Authenticating with Learn (${e})...
|
|
3
|
+
`);let r=await A(),s=k(e);if(s&&s.access_token){let l=await fetch(`${this.baseUrl}/api/auth/cli/check-authorization`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({machine_id:r.machineId,sdk_name:e})});if(l.ok){let y=await l.json();if(y.authorized)return console.log("This machine is already authorized."),console.log(`Logged in as: ${y.user_email||y.user_id}`),{success:!0,user:y.user_email||y.user_id}}}let n=await fetch(`${this.baseUrl}/api/auth/cli/device`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!n.ok)throw new o("Failed to generate device code",n.status);let i=await n.json();console.log(`
|
|
4
|
+
To authorize this device:`),console.log(`1. Visit: ${i.verification_uri}`),console.log(`2. Enter code: ${i.user_code}`),console.log("3. Complete 2FA verification"),console.log(`
|
|
4
5
|
Opening browser...
|
|
5
|
-
`),await this.openBrowser(
|
|
6
|
+
`),await this.openBrowser(i.verification_uri_complete);let a=!1,h=i.interval*1e3,u=Math.ceil(i.expires_in/i.interval),f=0;for(;f<u;){await this.sleep(h),f++;let l=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:i.device_code})});if(l.status===202){process.stdout.write(".");continue}if(l.ok){a=!0;break}}if(!a)throw new o("Device authorization timeout",408);console.log(`
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
Device authorized! Sending 2FA code...`);let p=await fetch(`${this.baseUrl}/api/auth/2fa/send`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:i.device_code,sdk_name:e,machine_id:r.machineId})});if(!p.ok){let l=await p.json().catch(()=>({}));throw new o(l.message||"Failed to send 2FA code",p.status)}console.log(`2FA code sent to your email.
|
|
9
|
+
`);let I=(await import("readline")).createInterface({input:process.stdin,output:process.stdout}),M=await new Promise(l=>{I.question("Enter 2FA code from email: ",y=>{I.close(),l(y)})}),T=await fetch(`${this.baseUrl}/api/auth/2fa/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:i.device_code,code:M.trim()})});if(!T.ok){let l=await T.json().catch(()=>({}));throw new o(l.message||"2FA verification failed",T.status)}let S=await T.json();return E(e,{access_token:S.access_token,refresh_token:null,expires_at:null,user_id:S.user_id,machine_id:r.machineId,sdk_name:e,authorized_at:new Date().toISOString()}),console.log(`
|
|
10
|
+
Authentication successful!`),console.log(`Logged in as: ${S.user_email||S.user_id}`),console.log(`This machine is now permanently authorized.
|
|
11
|
+
`),{success:!0,user:S.user_email||S.user_id}}catch(r){throw r instanceof o?r:new o(r.message||"Login failed",r.status||500)}}async isAuthenticated(){return X()}logout(){N(),console.log("Logged out successfully")}getAccessToken(){let e=q();if(!e)throw new o("Not authenticated. Run login() first.",401);if(e.expires_at&&new Date(e.expires_at)<=new Date)throw new o("Token expired. Run login() again.",401);return e.access_token}async request(e,r,s){let n=this.getAccessToken(),i={method:e,headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`}};s&&(i.body=JSON.stringify(s));let a=await fetch(`${this.baseUrl}${r}`,i);if(!a.ok){let h=await a.json().catch(()=>({}));throw new o(h.message||`Request failed: ${a.statusText}`,a.status,h)}return a.json()}async list(){return(await this.request("GET",`/api/projects/${this.appId}/secrets`)).secrets}async sync(e,r){return await this.request("POST",`/api/projects/${this.appId}/secrets/sync`,{secrets:e,options:{deleteMissing:r?.deleteMissing??!1,dryRun:r?.dryRun??!1}})}async importSecrets(e){return this.request("POST",`/api/projects/${this.appId}/import-secrets`,{version:e.version||"1.0",origins:e.origins,secrets:e.secrets})}async importFromFile(e){let{loadSecretsConfig:r}=await Promise.resolve().then(()=>(J(),Y)),s=r(e);return this.importSecrets(s)}};J();return oe(ue);})();
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import{a as
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import{a as U,b as q,c as N,d as F}from"./chunk-AS6G7JYX.mjs";var i=class extends Error{constructor(t,r,n){super(t);this.status=r;this.response=n;this.name="SecretsSDKError"}},y=class extends i{constructor(e="Origin not allowed for this token"){super(e,403),this.name="OriginMismatchError"}},w=class extends i{constructor(e="Rate limit exceeded",t=60,r=0,n=100){super(e,429),this.name="RateLimitError",this.retryAfter=t,this.remaining=r,this.limit=n}},S=class extends i{constructor(e="Invalid or expired token"){super(e,401),this.name="InvalidTokenError"}};var _=class{constructor(e={}){this.rateLimitInfo=null;this.appId=e.appId||null,this.token=e.token||e.sessionToken||null,this.baseUrl=e.baseUrl||"https://ctklearn.carsontkempf.workers.dev",this.timeout=e.timeout||3e4,this.retryOn429=e.retryOn429!==void 0?e.retryOn429:!0,this.zeroConfigMode=!this.appId&&!this.token}getUsage(){return this.rateLimitInfo}parseRateLimitHeaders(e){let t=e.get("X-RateLimit-Limit"),r=e.get("X-RateLimit-Remaining"),n=e.get("X-RateLimit-Reset");t&&r&&n&&(this.rateLimitInfo={limit:parseInt(t),remaining:parseInt(r),reset:parseInt(n)})}sleep(e){return new Promise(t=>setTimeout(t,e))}async fetchWithTimeout(e,t,r){let n=new AbortController,a=setTimeout(()=>n.abort(),r);try{return await fetch(e,{...t,signal:n.signal})}finally{clearTimeout(a)}}async call(e,t,r={}){let n=0,a=this.retryOn429?3:0;for(;;)try{let o=this.zeroConfigMode?`${this.baseUrl}/api/sdk/proxy`:`${this.baseUrl}/api/sdk/${this.appId}/proxy`,m={"Content-Type":"application/json"};!this.zeroConfigMode&&this.token&&(m.Authorization=`Bearer ${this.token}`);let l=await this.fetchWithTimeout(o,{method:"POST",headers:m,body:JSON.stringify({keyName:e,endpoint:t,method:r.method||"GET",body:r.body,headers:r.headers})},this.timeout);this.parseRateLimitHeaders(l.headers);let h=await l.json();if(!l.ok){let u=h.data?.message||h?.message||`Request failed with status ${l.status}`;if(l.status===403&&u.includes("Origin"))throw new y(u);if(l.status===401)throw new S(u);if(l.status===429){let M=this.rateLimitInfo?this.rateLimitInfo.reset-Math.floor(Date.now()/1e3):60,k=new w(u,M,this.rateLimitInfo?.remaining||0,this.rateLimitInfo?.limit||100);if(this.retryOn429&&n<a){n++;let T=Math.min(1e3*Math.pow(2,n-1),8e3);await this.sleep(T);continue}throw k}throw new i(u,l.status,h)}return h.data}catch(o){throw o instanceof i?o:o instanceof Error&&o.name==="AbortError"?new i("Request timeout",408):new i(o instanceof Error?o.message:"Unknown error occurred",500)}}async get(e,t,r){return this.call(e,t,{method:"GET",headers:r})}async post(e,t,r,n){return this.call(e,t,{method:"POST",body:r,headers:n})}async put(e,t,r,n){return this.call(e,t,{method:"PUT",body:r,headers:n})}async delete(e,t,r){return this.call(e,t,{method:"DELETE",headers:r})}async patch(e,t,r,n){return this.call(e,t,{method:"PATCH",body:r,headers:n})}setAppId(e){this.appId=e}async secretsRequest(e,t){if(!this.appId)throw new i("App ID required for secrets management. Call setAppId() first.",400);let r=await this.fetchWithTimeout(`${this.baseUrl}/api/projects/${this.appId}/secrets/manage?action=${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:t?JSON.stringify(t):void 0},this.timeout);if(!r.ok){let n=await r.json().catch(()=>({}));throw new i(n.message||`Secrets management failed: ${r.statusText}`,r.status,n)}return r.json()}async listSecrets(){return this.secretsRequest("list")}async createSecret(e){return this.secretsRequest("create",e)}async updateSecret(e){return this.secretsRequest("update",e)}async deleteSecret(e){return this.secretsRequest("delete",{name:e})}async syncEnv(e,t){return this.secretsRequest("sync",{secrets:e,provider:t})}};import*as c from"fs";import*as v from"path";import*as D from"os";function R(){let s=D.homedir(),e=v.join(s,".learn");return v.join(e,"credentials.json")}function z(){let s=D.homedir(),e=v.join(s,".learn");c.existsSync(e)||c.mkdirSync(e,{recursive:!0,mode:448})}function C(){try{let s=R();if(!c.existsSync(s))return null;let e=c.readFileSync(s,"utf-8"),t=JSON.parse(e);return t.access_token&&!t["learn-secrets-sdk"]&&!t["learn-auth-sdk"]?{"learn-secrets-sdk":t}:t}catch{return null}}function P(s){z();let e=R(),t=JSON.stringify(s,null,2);c.writeFileSync(e,t,{mode:384})}function b(s){let e=C();return e&&e[s]||null}function O(s,e){let t=C()||{};t[s]=e,P(t)}function J(s){let e=C();e&&(delete e[s],Object.keys(e).length===0?A():P(e))}function L(){return b("learn-secrets-sdk")}function A(){try{let s=R();c.existsSync(s)&&c.unlinkSync(s)}catch{}}function H(s){let e=b(s);return e!==null&&!!e.access_token}function K(){let s=L();return s?s.expires_at?new Date(s.expires_at)>new Date:!0:!1}import{exec as W}from"child_process";import{promisify as B}from"util";import{createHash as j}from"crypto";import*as p from"os";var x=B(W);async function E(){let s=process.platform;try{let e;if(s==="darwin")e=await G();else if(s==="linux")e=await V();else if(s==="win32")e=await X();else throw new Error(`Unsupported platform: ${s}`);let t=j("sha256").update(e).digest("hex").substring(0,32),r=JSON.stringify({machineId:t,cpus:p.cpus().length,arch:p.arch(),platform:p.platform(),homedir:p.homedir()}),n=j("sha256").update(r).digest("hex");return{machineId:t,fingerprint:n}}catch(e){throw new Error(`Failed to get machine ID: ${e.message}`)}}async function G(){try{let{stdout:s}=await x(`ioreg -l | grep IOPlatformSerialNumber | awk '{print $4}' | sed 's/"//g'`),{stdout:e}=await x("hostname"),t=s.trim(),r=e.trim();if(t&&r)return`${t}-${r}`;let{stdout:n}=await x(`ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID | awk '{print $3}' | sed 's/"//g'`);return n.trim()}catch{throw new Error("Failed to get macOS machine ID")}}async function V(){try{let{stdout:s}=await x("cat /etc/machine-id 2>/dev/null || cat /var/lib/dbus/machine-id 2>/dev/null"),e=s.trim();if(e)return e;throw new Error("Machine ID file not found")}catch{throw new Error("Failed to get Linux machine ID")}}async function X(){try{let{stdout:s}=await x("wmic csproduct get uuid"),t=s.split(`
|
|
2
|
+
`).map(r=>r.trim()).find(r=>r&&r!=="UUID");if(t)return t;throw new Error("Failed to parse machine UUID from WMIC")}catch{throw new Error("Failed to get Windows machine ID")}}var $=class{constructor(e){this.appId=e.appId,this.baseUrl=e.baseUrl||"https://ctklearn.carsontkempf.workers.dev",this.timeout=e.timeout||3e4}async openBrowser(e){let{exec:t}=await import("child_process"),{promisify:r}=await import("util"),n=r(t),a=process.platform;try{a==="darwin"?await n(`open "${e}"`):a==="win32"?await n(`start "${e}"`):await n(`xdg-open "${e}"`)}catch(o){throw console.error("Failed to open browser:",o),new i("Failed to open browser",500)}}sleep(e){return new Promise(t=>setTimeout(t,e))}async login(){let e="learn-secrets-sdk";try{console.log(`Authenticating with Learn (${e})...
|
|
3
|
+
`);let t=await E(),r=b(e);if(r&&r.access_token){let d=await fetch(`${this.baseUrl}/api/auth/cli/check-authorization`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({machine_id:t.machineId,sdk_name:e})});if(d.ok){let g=await d.json();if(g.authorized)return console.log("This machine is already authorized."),console.log(`Logged in as: ${g.user_email||g.user_id}`),{success:!0,user:g.user_email||g.user_id}}}let n=await fetch(`${this.baseUrl}/api/auth/cli/device`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!n.ok)throw new i("Failed to generate device code",n.status);let a=await n.json();console.log(`
|
|
4
|
+
To authorize this device:`),console.log(`1. Visit: ${a.verification_uri}`),console.log(`2. Enter code: ${a.user_code}`),console.log("3. Complete 2FA verification"),console.log(`
|
|
4
5
|
Opening browser...
|
|
5
|
-
`),await this.openBrowser(
|
|
6
|
+
`),await this.openBrowser(a.verification_uri_complete);let o=!1,m=a.interval*1e3,l=Math.ceil(a.expires_in/a.interval),h=0;for(;h<l;){await this.sleep(m),h++;let d=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:a.device_code})});if(d.status===202){process.stdout.write(".");continue}if(d.ok){o=!0;break}}if(!o)throw new i("Device authorization timeout",408);console.log(`
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
Device authorized! Sending 2FA code...`);let u=await fetch(`${this.baseUrl}/api/auth/2fa/send`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:a.device_code,sdk_name:e,machine_id:t.machineId})});if(!u.ok){let d=await u.json().catch(()=>({}));throw new i(d.message||"Failed to send 2FA code",u.status)}console.log(`2FA code sent to your email.
|
|
9
|
+
`);let k=(await import("readline")).createInterface({input:process.stdin,output:process.stdout}),T=await new Promise(d=>{k.question("Enter 2FA code from email: ",g=>{k.close(),d(g)})}),I=await fetch(`${this.baseUrl}/api/auth/2fa/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:a.device_code,code:T.trim()})});if(!I.ok){let d=await I.json().catch(()=>({}));throw new i(d.message||"2FA verification failed",I.status)}let f=await I.json();return O(e,{access_token:f.access_token,refresh_token:null,expires_at:null,user_id:f.user_id,machine_id:t.machineId,sdk_name:e,authorized_at:new Date().toISOString()}),console.log(`
|
|
10
|
+
Authentication successful!`),console.log(`Logged in as: ${f.user_email||f.user_id}`),console.log(`This machine is now permanently authorized.
|
|
11
|
+
`),{success:!0,user:f.user_email||f.user_id}}catch(t){throw t instanceof i?t:new i(t.message||"Login failed",t.status||500)}}async isAuthenticated(){return K()}logout(){A(),console.log("Logged out successfully")}getAccessToken(){let e=L();if(!e)throw new i("Not authenticated. Run login() first.",401);if(e.expires_at&&new Date(e.expires_at)<=new Date)throw new i("Token expired. Run login() again.",401);return e.access_token}async request(e,t,r){let n=this.getAccessToken(),a={method:e,headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`}};r&&(a.body=JSON.stringify(r));let o=await fetch(`${this.baseUrl}${t}`,a);if(!o.ok){let m=await o.json().catch(()=>({}));throw new i(m.message||`Request failed: ${o.statusText}`,o.status,m)}return o.json()}async list(){return(await this.request("GET",`/api/projects/${this.appId}/secrets`)).secrets}async sync(e,t){return await this.request("POST",`/api/projects/${this.appId}/secrets/sync`,{secrets:e,options:{deleteMissing:t?.deleteMissing??!1,dryRun:t?.dryRun??!1}})}async importSecrets(e){return this.request("POST",`/api/projects/${this.appId}/import-secrets`,{version:e.version||"1.0",origins:e.origins,secrets:e.secrets})}async importFromFile(e){let{loadSecretsConfig:t}=await import("./env-resolver-Y4SFGOKB.mjs"),r=t(e);return this.importSecrets(r)}};export{S as InvalidTokenError,y as OriginMismatchError,w as RateLimitError,$ as SecretsManagement,_ as SecretsSDK,i as SecretsSDKError,J as deleteSDKCredentials,E as getMachineId,H as hasSDKCredentials,F as loadSecretsConfig,C as readAllCredentials,b as readSDKCredentials,q as resolveEnvInSecret,N as resolveEnvInSecrets,U as resolveEnvString,P as writeAllCredentials,O as writeSDKCredentials};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "learn-secrets-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Secure API proxy SDK for static sites with domain-scoped tokens",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
],
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "https://github.com/carsontkempf/learn.git",
|
|
18
|
+
"url": "git+https://github.com/carsontkempf/learn.git",
|
|
19
19
|
"directory": "packages/secrets-sdk"
|
|
20
20
|
},
|
|
21
21
|
"homepage": "https://github.com/carsontkempf/learn/tree/main/packages/secrets-sdk",
|