learn-secrets-sdk 1.6.8 → 1.8.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/dist/cli/index.mjs +422 -268
- package/dist/index.d.mts +114 -4
- package/dist/index.d.ts +114 -4
- package/dist/index.global.js +6 -6
- package/dist/index.mjs +5 -5
- package/package.json +2 -2
package/dist/cli/index.mjs
CHANGED
|
@@ -362,7 +362,7 @@ Log in on ${this.baseUrl}`);
|
|
|
362
362
|
/**
|
|
363
363
|
* Make authenticated request
|
|
364
364
|
*/
|
|
365
|
-
async request(method,
|
|
365
|
+
async request(method, path3, body) {
|
|
366
366
|
const token = this.getAccessToken();
|
|
367
367
|
const options = {
|
|
368
368
|
method,
|
|
@@ -374,7 +374,7 @@ Log in on ${this.baseUrl}`);
|
|
|
374
374
|
if (body) {
|
|
375
375
|
options.body = JSON.stringify(body);
|
|
376
376
|
}
|
|
377
|
-
const response = await fetch(`${this.baseUrl}${
|
|
377
|
+
const response = await fetch(`${this.baseUrl}${path3}`, options);
|
|
378
378
|
if (!response.ok) {
|
|
379
379
|
const errorData = await response.json().catch(() => ({}));
|
|
380
380
|
throw new SecretsSDKError(
|
|
@@ -587,43 +587,6 @@ var PROVIDER_PATTERNS = [
|
|
|
587
587
|
authPrefix: "Bearer "
|
|
588
588
|
}
|
|
589
589
|
];
|
|
590
|
-
function isLikelyApiKey(key, value) {
|
|
591
|
-
const keyPatterns = [
|
|
592
|
-
/_API_KEY$/i,
|
|
593
|
-
/_SECRET$/i,
|
|
594
|
-
/_TOKEN$/i,
|
|
595
|
-
/^API_KEY_/i,
|
|
596
|
-
/^SECRET_/i,
|
|
597
|
-
/^TOKEN_/i
|
|
598
|
-
];
|
|
599
|
-
for (const pattern of keyPatterns) {
|
|
600
|
-
if (pattern.test(key)) {
|
|
601
|
-
return true;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
const valuePatterns = [
|
|
605
|
-
/^sk-[a-zA-Z0-9_-]+$/,
|
|
606
|
-
// OpenAI, Stripe
|
|
607
|
-
/^sk-ant-[a-zA-Z0-9_-]+$/,
|
|
608
|
-
// Anthropic
|
|
609
|
-
/^ghp_[a-zA-Z0-9]+$/,
|
|
610
|
-
// GitHub
|
|
611
|
-
/^ya29\.[a-zA-Z0-9_-]+$/,
|
|
612
|
-
// Google OAuth
|
|
613
|
-
/^[a-f0-9]{32}$/,
|
|
614
|
-
// 32-char hex
|
|
615
|
-
/^[a-f0-9]{40}$/,
|
|
616
|
-
// 40-char hex (like GitHub)
|
|
617
|
-
/^[a-zA-Z0-9_-]{40,}$/
|
|
618
|
-
// Long random string
|
|
619
|
-
];
|
|
620
|
-
for (const pattern of valuePatterns) {
|
|
621
|
-
if (pattern.test(value)) {
|
|
622
|
-
return true;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
return false;
|
|
626
|
-
}
|
|
627
590
|
function detectProvider(key) {
|
|
628
591
|
for (const pattern of PROVIDER_PATTERNS) {
|
|
629
592
|
if (pattern.pattern.test(key)) {
|
|
@@ -633,12 +596,12 @@ function detectProvider(key) {
|
|
|
633
596
|
return null;
|
|
634
597
|
}
|
|
635
598
|
function envKeyToSecretName(key) {
|
|
636
|
-
return key.toLowerCase().replace(/
|
|
599
|
+
return key.toLowerCase().replace(/_/g, "-");
|
|
637
600
|
}
|
|
638
|
-
function
|
|
601
|
+
function convertAllEnvVarsToSecrets(envVars) {
|
|
639
602
|
const secrets = [];
|
|
640
603
|
for (const { key, value } of envVars) {
|
|
641
|
-
if (!
|
|
604
|
+
if (!value || value.trim() === "") {
|
|
642
605
|
continue;
|
|
643
606
|
}
|
|
644
607
|
const providerInfo = detectProvider(key);
|
|
@@ -658,7 +621,6 @@ function detectApiKeys(envVars) {
|
|
|
658
621
|
provider: "custom",
|
|
659
622
|
api_key: value,
|
|
660
623
|
base_url: "",
|
|
661
|
-
// User must configure
|
|
662
624
|
auth_header: "Authorization",
|
|
663
625
|
auth_prefix: "Bearer "
|
|
664
626
|
});
|
|
@@ -668,16 +630,117 @@ function detectApiKeys(envVars) {
|
|
|
668
630
|
}
|
|
669
631
|
function summarizeDetectedSecrets(secrets) {
|
|
670
632
|
if (secrets.length === 0) {
|
|
671
|
-
return "No
|
|
633
|
+
return "No secrets found in .env file";
|
|
672
634
|
}
|
|
673
|
-
const lines = ["Detected
|
|
635
|
+
const lines = ["Detected secrets:"];
|
|
674
636
|
for (const secret of secrets) {
|
|
675
|
-
const masked = secret.api_key.substring(0, 8) + "...";
|
|
637
|
+
const masked = secret.api_key.length > 8 ? secret.api_key.substring(0, 8) + "..." : "***";
|
|
676
638
|
lines.push(` - ${secret.name} (${secret.provider}): ${masked}`);
|
|
677
639
|
}
|
|
678
640
|
return lines.join("\n");
|
|
679
641
|
}
|
|
680
642
|
|
|
643
|
+
// src/cli/commands/connect-cloudflare.ts
|
|
644
|
+
function getApiUrl(baseUrl) {
|
|
645
|
+
return baseUrl || "https://cloudprototype.org";
|
|
646
|
+
}
|
|
647
|
+
async function connectCloudflare(apiToken, baseUrl) {
|
|
648
|
+
if (!apiToken.match(/^[A-Za-z0-9_-]{40,}$/)) {
|
|
649
|
+
return {
|
|
650
|
+
success: false,
|
|
651
|
+
error: "Invalid API token format. Token should be 40+ alphanumeric characters"
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
const validation = await validateCloudflareToken(apiToken);
|
|
655
|
+
if (!validation.valid) {
|
|
656
|
+
return {
|
|
657
|
+
success: false,
|
|
658
|
+
error: validation.error || "Token validation failed"
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
const apiUrl = getApiUrl(baseUrl);
|
|
663
|
+
const response = await fetch(`${apiUrl}/api/cloudflare/connect`, {
|
|
664
|
+
method: "POST",
|
|
665
|
+
headers: {
|
|
666
|
+
"Content-Type": "application/json"
|
|
667
|
+
},
|
|
668
|
+
credentials: "include",
|
|
669
|
+
body: JSON.stringify({
|
|
670
|
+
apiToken,
|
|
671
|
+
accountId: validation.accountId,
|
|
672
|
+
accountName: validation.accountName
|
|
673
|
+
})
|
|
674
|
+
});
|
|
675
|
+
if (!response.ok) {
|
|
676
|
+
const error = await response.json();
|
|
677
|
+
return {
|
|
678
|
+
success: false,
|
|
679
|
+
error: error.message || "Failed to store token"
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
success: true,
|
|
684
|
+
accountId: validation.accountId,
|
|
685
|
+
accountName: validation.accountName
|
|
686
|
+
};
|
|
687
|
+
} catch (error) {
|
|
688
|
+
return {
|
|
689
|
+
success: false,
|
|
690
|
+
error: error.message || "Failed to connect Cloudflare account"
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
async function validateCloudflareToken(token) {
|
|
695
|
+
try {
|
|
696
|
+
const response = await fetch("https://api.cloudflare.com/client/v4/user", {
|
|
697
|
+
headers: {
|
|
698
|
+
Authorization: `Bearer ${token}`,
|
|
699
|
+
"Content-Type": "application/json"
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
if (!response.ok) {
|
|
703
|
+
const error = await response.json();
|
|
704
|
+
return {
|
|
705
|
+
valid: false,
|
|
706
|
+
error: error.errors?.[0]?.message || "Invalid token"
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
const data = await response.json();
|
|
710
|
+
const accountsResponse = await fetch("https://api.cloudflare.com/client/v4/accounts", {
|
|
711
|
+
headers: {
|
|
712
|
+
Authorization: `Bearer ${token}`,
|
|
713
|
+
"Content-Type": "application/json"
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
if (!accountsResponse.ok) {
|
|
717
|
+
return {
|
|
718
|
+
valid: false,
|
|
719
|
+
error: "Token does not have access to accounts"
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
const accountsData = await accountsResponse.json();
|
|
723
|
+
const accounts = accountsData.result || [];
|
|
724
|
+
if (accounts.length === 0) {
|
|
725
|
+
return {
|
|
726
|
+
valid: false,
|
|
727
|
+
error: "No accounts found for this token"
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
const account = accounts[0];
|
|
731
|
+
return {
|
|
732
|
+
valid: true,
|
|
733
|
+
accountId: account.id,
|
|
734
|
+
accountName: account.name
|
|
735
|
+
};
|
|
736
|
+
} catch (error) {
|
|
737
|
+
return {
|
|
738
|
+
valid: false,
|
|
739
|
+
error: error.message || "Failed to validate token"
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
681
744
|
// src/cli/commands/init.ts
|
|
682
745
|
async function initCommand(options) {
|
|
683
746
|
if (!hasValidCredentials()) {
|
|
@@ -720,12 +783,10 @@ Onboarding site for project: ${projectId}`);
|
|
|
720
783
|
try {
|
|
721
784
|
console.log(`Reading ${envFile}...`);
|
|
722
785
|
const envVars = parseEnvFile(envFile);
|
|
723
|
-
secrets =
|
|
786
|
+
secrets = convertAllEnvVarsToSecrets(envVars);
|
|
724
787
|
if (secrets.length === 0) {
|
|
725
|
-
console.log("\nNo
|
|
726
|
-
console.log("Make sure your .env
|
|
727
|
-
console.log(" OPENAI_API_KEY=sk-...");
|
|
728
|
-
console.log(" ANTHROPIC_API_KEY=sk-ant-...");
|
|
788
|
+
console.log("\nNo environment variables found in .env file.");
|
|
789
|
+
console.log("Make sure your .env file is not empty.");
|
|
729
790
|
process.exit(0);
|
|
730
791
|
}
|
|
731
792
|
console.log("\n" + summarizeDetectedSecrets(secrets));
|
|
@@ -773,11 +834,62 @@ Onboarding site for project: ${projectId}`);
|
|
|
773
834
|
console.log("\nAdd to your static site code:");
|
|
774
835
|
console.log(" const sdk = new SecretsSDK();");
|
|
775
836
|
console.log(" // Uses origin-based auth - no token needed!");
|
|
837
|
+
console.log("\n--- Cloudflare Worker Setup ---");
|
|
776
838
|
const dashboardUrl = options.baseUrl || "https://cloudprototype.org";
|
|
839
|
+
let cloudflareConnected = false;
|
|
840
|
+
try {
|
|
841
|
+
const statusResponse = await fetch(`${dashboardUrl}/api/cloudflare/status`, {
|
|
842
|
+
credentials: "include"
|
|
843
|
+
});
|
|
844
|
+
cloudflareConnected = statusResponse.ok;
|
|
845
|
+
} catch {
|
|
846
|
+
cloudflareConnected = false;
|
|
847
|
+
}
|
|
848
|
+
if (cloudflareConnected) {
|
|
849
|
+
console.log("Cloudflare account is already connected.");
|
|
850
|
+
} else {
|
|
851
|
+
console.log("To deploy secrets via Cloudflare Worker, we need your Cloudflare API token.");
|
|
852
|
+
console.log("\nRequired permissions:");
|
|
853
|
+
console.log(" - Workers Scripts: Edit");
|
|
854
|
+
console.log(" - Workers Routes: Edit");
|
|
855
|
+
console.log(" - Account Settings: Read");
|
|
856
|
+
console.log("\nCreate token at: https://dash.cloudflare.com/profile/api-tokens");
|
|
857
|
+
const readline = await import("readline");
|
|
858
|
+
const rl = readline.createInterface({
|
|
859
|
+
input: process.stdin,
|
|
860
|
+
output: process.stdout
|
|
861
|
+
});
|
|
862
|
+
const apiToken = await new Promise((resolve2) => {
|
|
863
|
+
rl.question("\nCloudflare API Token (or press Enter to skip): ", (answer) => {
|
|
864
|
+
rl.close();
|
|
865
|
+
resolve2(answer.trim());
|
|
866
|
+
});
|
|
867
|
+
});
|
|
868
|
+
if (apiToken) {
|
|
869
|
+
console.log("\nValidating token...");
|
|
870
|
+
const result2 = await connectCloudflare(apiToken, options.baseUrl);
|
|
871
|
+
if (result2.success) {
|
|
872
|
+
console.log("Cloudflare account connected");
|
|
873
|
+
console.log(` Account: ${result2.accountName}`);
|
|
874
|
+
cloudflareConnected = true;
|
|
875
|
+
} else {
|
|
876
|
+
console.log(`
|
|
877
|
+
Warning: Failed to connect Cloudflare: ${result2.error}`);
|
|
878
|
+
console.log("You can connect later by running: learn-secrets deploy");
|
|
879
|
+
}
|
|
880
|
+
} else {
|
|
881
|
+
console.log("\nSkipped Cloudflare connection.");
|
|
882
|
+
console.log("You can connect later when you run: learn-secrets deploy");
|
|
883
|
+
}
|
|
884
|
+
}
|
|
777
885
|
console.log(`
|
|
778
886
|
Next steps:`);
|
|
779
887
|
console.log(` 1. Run: learn-secrets deploy`);
|
|
780
|
-
|
|
888
|
+
if (cloudflareConnected) {
|
|
889
|
+
console.log(` (Deploy Worker to Cloudflare with your secrets)`);
|
|
890
|
+
} else {
|
|
891
|
+
console.log(` (Connect Cloudflare and deploy Worker with your secrets)`);
|
|
892
|
+
}
|
|
781
893
|
console.log(` 2. Visit: ${dashboardUrl}/dashboard to manage secrets`);
|
|
782
894
|
const config = {
|
|
783
895
|
project: projectId,
|
|
@@ -798,212 +910,6 @@ Updated local config: ${configFile}`);
|
|
|
798
910
|
}
|
|
799
911
|
}
|
|
800
912
|
|
|
801
|
-
// src/cli/commands/setup.ts
|
|
802
|
-
async function setupCommand(options) {
|
|
803
|
-
const baseUrl = options.baseUrl || "https://cloudprototype.org";
|
|
804
|
-
const envFile = options.env || ".env";
|
|
805
|
-
const projectName = options.projectName || `Project - ${options.origin}`;
|
|
806
|
-
console.log("\n\u{1F680} Learn Secrets Setup\n");
|
|
807
|
-
console.log(`Email: ${options.email}`);
|
|
808
|
-
console.log(`Origin: ${options.origin}`);
|
|
809
|
-
console.log(`Project: ${projectName}`);
|
|
810
|
-
console.log(`Env File: ${envFile}
|
|
811
|
-
`);
|
|
812
|
-
let secrets;
|
|
813
|
-
try {
|
|
814
|
-
console.log(`\u{1F4D6} Reading ${envFile}...`);
|
|
815
|
-
const envVars = parseEnvFile(envFile);
|
|
816
|
-
secrets = detectApiKeys(envVars);
|
|
817
|
-
if (secrets.length === 0) {
|
|
818
|
-
console.log("\n\u26A0\uFE0F No API keys detected in .env file.");
|
|
819
|
-
console.log("Make sure your .env contains variables like:");
|
|
820
|
-
console.log(" OPENAI_API_KEY=sk-...");
|
|
821
|
-
console.log(" STRIPE_API_KEY=sk_live_...");
|
|
822
|
-
console.log(" SPOTIFY_CLIENT_ID=abc123...");
|
|
823
|
-
process.exit(0);
|
|
824
|
-
}
|
|
825
|
-
console.log("\n" + summarizeDetectedSecrets(secrets));
|
|
826
|
-
console.log("");
|
|
827
|
-
} catch (error) {
|
|
828
|
-
console.error(`\u274C Error reading ${envFile}:`, error.message);
|
|
829
|
-
process.exit(1);
|
|
830
|
-
}
|
|
831
|
-
if (!options.yes) {
|
|
832
|
-
const readline = await import("readline");
|
|
833
|
-
const rl = readline.createInterface({
|
|
834
|
-
input: process.stdin,
|
|
835
|
-
output: process.stdout
|
|
836
|
-
});
|
|
837
|
-
const answer = await new Promise((resolve2) => {
|
|
838
|
-
rl.question("Continue with setup? (y/N): ", resolve2);
|
|
839
|
-
});
|
|
840
|
-
rl.close();
|
|
841
|
-
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
842
|
-
console.log("\u274C Cancelled.");
|
|
843
|
-
process.exit(0);
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
console.log("\n\u{1F510} Authenticating...");
|
|
847
|
-
let cookies;
|
|
848
|
-
try {
|
|
849
|
-
const loginResponse = await fetch(`${baseUrl}/api/login`, {
|
|
850
|
-
method: "POST",
|
|
851
|
-
headers: {
|
|
852
|
-
"Content-Type": "application/json"
|
|
853
|
-
},
|
|
854
|
-
body: JSON.stringify({
|
|
855
|
-
email: options.email,
|
|
856
|
-
password: options.password
|
|
857
|
-
})
|
|
858
|
-
});
|
|
859
|
-
if (!loginResponse.ok) {
|
|
860
|
-
const errorData = await loginResponse.json().catch(() => ({}));
|
|
861
|
-
throw new Error(errorData.error || errorData.message || "Authentication failed");
|
|
862
|
-
}
|
|
863
|
-
const setCookieHeader = loginResponse.headers.get("set-cookie");
|
|
864
|
-
if (!setCookieHeader) {
|
|
865
|
-
throw new Error("No session cookie received");
|
|
866
|
-
}
|
|
867
|
-
const cookieParts = setCookieHeader.split(";")[0];
|
|
868
|
-
cookies = cookieParts;
|
|
869
|
-
console.log("\u2705 Authenticated successfully");
|
|
870
|
-
} catch (error) {
|
|
871
|
-
console.error(`\u274C Authentication failed: ${error.message}`);
|
|
872
|
-
console.log("\n\u{1F4A1} Make sure:");
|
|
873
|
-
console.log(" - Email and password are correct");
|
|
874
|
-
console.log(" - User account exists in the system");
|
|
875
|
-
console.log(" - Backend is accessible at", baseUrl);
|
|
876
|
-
process.exit(1);
|
|
877
|
-
}
|
|
878
|
-
console.log("\n\u{1F4E6} Creating project...");
|
|
879
|
-
let projectAppId;
|
|
880
|
-
try {
|
|
881
|
-
const createProjectResponse = await fetch(`${baseUrl}/api/projects`, {
|
|
882
|
-
method: "POST",
|
|
883
|
-
headers: {
|
|
884
|
-
"Content-Type": "application/json",
|
|
885
|
-
"Cookie": cookies
|
|
886
|
-
},
|
|
887
|
-
body: JSON.stringify({
|
|
888
|
-
name: projectName,
|
|
889
|
-
description: `Auto-provisioned for ${options.origin}`
|
|
890
|
-
})
|
|
891
|
-
});
|
|
892
|
-
if (!createProjectResponse.ok) {
|
|
893
|
-
const errorData = await createProjectResponse.json().catch(() => ({}));
|
|
894
|
-
throw new Error(errorData.message || "Failed to create project");
|
|
895
|
-
}
|
|
896
|
-
const projectData = await createProjectResponse.json();
|
|
897
|
-
projectAppId = projectData.project.appid;
|
|
898
|
-
console.log(`\u2705 Project created: ${projectAppId}`);
|
|
899
|
-
} catch (error) {
|
|
900
|
-
console.error(`\u274C Project creation failed: ${error.message}`);
|
|
901
|
-
process.exit(1);
|
|
902
|
-
}
|
|
903
|
-
console.log("\n\u{1F511} Generating SDK token...");
|
|
904
|
-
let sdkToken;
|
|
905
|
-
try {
|
|
906
|
-
const tokenResponse = await fetch(`${baseUrl}/api/projects/${projectAppId}/sdk-tokens`, {
|
|
907
|
-
method: "POST",
|
|
908
|
-
headers: {
|
|
909
|
-
"Content-Type": "application/json",
|
|
910
|
-
"Cookie": cookies
|
|
911
|
-
},
|
|
912
|
-
body: JSON.stringify({
|
|
913
|
-
label: `Production - ${options.origin}`,
|
|
914
|
-
origins: [options.origin],
|
|
915
|
-
expiresInDays: 365
|
|
916
|
-
})
|
|
917
|
-
});
|
|
918
|
-
if (!tokenResponse.ok) {
|
|
919
|
-
const errorData = await tokenResponse.json().catch(() => ({}));
|
|
920
|
-
throw new Error(errorData.message || "Failed to generate SDK token");
|
|
921
|
-
}
|
|
922
|
-
const tokenData = await tokenResponse.json();
|
|
923
|
-
sdkToken = tokenData.token;
|
|
924
|
-
console.log(`\u2705 SDK token generated`);
|
|
925
|
-
console.log(` Token: ${sdkToken.substring(0, 15)}...${sdkToken.slice(-4)}`);
|
|
926
|
-
} catch (error) {
|
|
927
|
-
console.error(`\u274C Token generation failed: ${error.message}`);
|
|
928
|
-
process.exit(1);
|
|
929
|
-
}
|
|
930
|
-
console.log("\n\u{1F4E4} Uploading secrets...");
|
|
931
|
-
try {
|
|
932
|
-
const importResponse = await fetch(`${baseUrl}/api/projects/${projectAppId}/import-secrets`, {
|
|
933
|
-
method: "POST",
|
|
934
|
-
headers: {
|
|
935
|
-
"Content-Type": "application/json",
|
|
936
|
-
"Cookie": cookies
|
|
937
|
-
},
|
|
938
|
-
body: JSON.stringify({
|
|
939
|
-
version: "1.0",
|
|
940
|
-
secrets
|
|
941
|
-
})
|
|
942
|
-
});
|
|
943
|
-
if (!importResponse.ok) {
|
|
944
|
-
const errorData = await importResponse.json().catch(() => ({}));
|
|
945
|
-
throw new Error(errorData.message || "Failed to upload secrets");
|
|
946
|
-
}
|
|
947
|
-
const result = await importResponse.json();
|
|
948
|
-
console.log("\u2705 Secrets uploaded:");
|
|
949
|
-
console.log(` Created: ${result.results.created}`);
|
|
950
|
-
console.log(` Updated: ${result.results.updated}`);
|
|
951
|
-
if (result.results.failed > 0) {
|
|
952
|
-
console.log(` \u26A0\uFE0F Failed: ${result.results.failed}`);
|
|
953
|
-
for (const failure of result.results.details.failed) {
|
|
954
|
-
console.log(` - ${failure.name}: ${failure.error}`);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
} catch (error) {
|
|
958
|
-
console.error(`\u274C Upload failed: ${error.message}`);
|
|
959
|
-
}
|
|
960
|
-
console.log("\n" + "=".repeat(60));
|
|
961
|
-
console.log("\u2705 SETUP COMPLETE!");
|
|
962
|
-
console.log("=".repeat(60));
|
|
963
|
-
console.log("\nConfiguration:");
|
|
964
|
-
console.log(` LEARN_TENANT_URL: ${baseUrl}/api/auth`);
|
|
965
|
-
console.log(` LEARN_PROJECT_APPID: ${projectAppId}`);
|
|
966
|
-
console.log(` LEARN_SDK_TOKEN: ${sdkToken}`);
|
|
967
|
-
console.log(` LEARN_ORIGIN: ${options.origin}`);
|
|
968
|
-
console.log("\nUsage in your static site:");
|
|
969
|
-
console.log(' <script src="https://unpkg.com/learn-secrets-sdk/dist/index.global.js"></script>');
|
|
970
|
-
console.log(" <script>");
|
|
971
|
-
console.log(" // Zero-config mode - no tokens needed in client code!");
|
|
972
|
-
console.log(" const sdk = new SecretsSDK.SecretsSDK();");
|
|
973
|
-
console.log(` sdk.setAppId('${projectAppId}');`);
|
|
974
|
-
console.log(" ");
|
|
975
|
-
console.log(" // Use your secrets");
|
|
976
|
-
console.log(' const data = await sdk.get("newsapi", "/v2/top-headlines");');
|
|
977
|
-
console.log(" </script>");
|
|
978
|
-
console.log("\nManage secrets:");
|
|
979
|
-
console.log(` Visit: ${baseUrl}`);
|
|
980
|
-
console.log(` Or use CLI: learn-secrets init --project ${projectAppId} --origins ${options.origin}`);
|
|
981
|
-
console.log("\n" + "=".repeat(60));
|
|
982
|
-
const configPath = ".env.learn";
|
|
983
|
-
const configContent = `# Learn Platform Configuration
|
|
984
|
-
# Generated on ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
985
|
-
# DO NOT COMMIT THIS FILE TO GIT
|
|
986
|
-
|
|
987
|
-
LEARN_TENANT_URL=${baseUrl}/api/auth
|
|
988
|
-
LEARN_PROJECT_APPID=${projectAppId}
|
|
989
|
-
LEARN_SDK_TOKEN=${sdkToken}
|
|
990
|
-
LEARN_ORIGIN=${options.origin}
|
|
991
|
-
|
|
992
|
-
# Token expires: 365 days
|
|
993
|
-
# Token prefix: ${sdkToken.slice(-4)}
|
|
994
|
-
`;
|
|
995
|
-
try {
|
|
996
|
-
const fs6 = await import("fs");
|
|
997
|
-
fs6.writeFileSync(configPath, configContent, "utf-8");
|
|
998
|
-
console.log(`
|
|
999
|
-
\u{1F4BE} Configuration saved to: ${configPath}`);
|
|
1000
|
-
} catch (error) {
|
|
1001
|
-
console.error(`
|
|
1002
|
-
\u26A0\uFE0F Could not save configuration file: ${error.message}`);
|
|
1003
|
-
}
|
|
1004
|
-
console.log("\n\u{1F389} Your site is ready to use Learn Secrets!\n");
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
913
|
// src/cli/commands/config.ts
|
|
1008
914
|
import fs4 from "fs";
|
|
1009
915
|
async function configCommand(options) {
|
|
@@ -1045,22 +951,270 @@ Configuration saved to: ${configFile}`);
|
|
|
1045
951
|
|
|
1046
952
|
// src/cli/commands/deploy.ts
|
|
1047
953
|
import fs5 from "fs";
|
|
1048
|
-
|
|
954
|
+
|
|
955
|
+
// src/cli/commands/provision-worker.ts
|
|
956
|
+
import path2 from "path";
|
|
957
|
+
import { fileURLToPath } from "url";
|
|
958
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
959
|
+
var __dirname = path2.dirname(__filename);
|
|
960
|
+
function getApiUrl2(baseUrl) {
|
|
961
|
+
return baseUrl || "https://cloudprototype.org";
|
|
962
|
+
}
|
|
963
|
+
async function provisionWorker(workerName, origins, envVars, baseUrl) {
|
|
964
|
+
const publicVars = [];
|
|
965
|
+
const secretVars = [];
|
|
966
|
+
for (const { key, value } of envVars) {
|
|
967
|
+
if (isPublicConfig(key)) {
|
|
968
|
+
publicVars.push({ key, value });
|
|
969
|
+
} else {
|
|
970
|
+
secretVars.push({ key, value });
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
try {
|
|
974
|
+
const apiUrl = getApiUrl2(baseUrl);
|
|
975
|
+
const deployResponse = await fetch(`${apiUrl}/api/cloudflare/provision-worker`, {
|
|
976
|
+
method: "POST",
|
|
977
|
+
headers: {
|
|
978
|
+
"Content-Type": "application/json"
|
|
979
|
+
},
|
|
980
|
+
credentials: "include",
|
|
981
|
+
body: JSON.stringify({
|
|
982
|
+
workerName,
|
|
983
|
+
allowedOrigins: origins,
|
|
984
|
+
publicVars: publicVars.map((v) => ({
|
|
985
|
+
key: `PUBLIC_${v.key}`,
|
|
986
|
+
value: v.value
|
|
987
|
+
})),
|
|
988
|
+
secrets: secretVars.map((v) => ({
|
|
989
|
+
key: `SECRET_${v.key}`,
|
|
990
|
+
value: v.value
|
|
991
|
+
}))
|
|
992
|
+
})
|
|
993
|
+
});
|
|
994
|
+
if (!deployResponse.ok) {
|
|
995
|
+
const error = await deployResponse.json();
|
|
996
|
+
return {
|
|
997
|
+
success: false,
|
|
998
|
+
error: error.message || "Failed to deploy Worker"
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
const result = await deployResponse.json();
|
|
1002
|
+
return {
|
|
1003
|
+
success: true,
|
|
1004
|
+
workerUrl: result.workerUrl,
|
|
1005
|
+
workerName: result.workerName
|
|
1006
|
+
};
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
return {
|
|
1009
|
+
success: false,
|
|
1010
|
+
error: error.message || "Failed to provision Worker"
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
function isPublicConfig(key) {
|
|
1015
|
+
const publicPatterns = [
|
|
1016
|
+
/_DOMAIN$/,
|
|
1017
|
+
/_CLIENT_ID$/,
|
|
1018
|
+
/_AUDIENCE$/,
|
|
1019
|
+
/_CALLBACK_URL$/,
|
|
1020
|
+
/_ISSUER$/,
|
|
1021
|
+
/_TEAM_ID$/,
|
|
1022
|
+
/_KEY_ID$/,
|
|
1023
|
+
/^API_BASE_URL$/,
|
|
1024
|
+
/^PUBLIC_/
|
|
1025
|
+
];
|
|
1026
|
+
for (const pattern of publicPatterns) {
|
|
1027
|
+
if (pattern.test(key)) {
|
|
1028
|
+
return true;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// src/cli/commands/deploy.ts
|
|
1035
|
+
async function deployCommand(options = {}) {
|
|
1036
|
+
if (!hasValidCredentials()) {
|
|
1037
|
+
console.error('Error: Not authenticated. Run "learn-secrets login" first.');
|
|
1038
|
+
process.exit(1);
|
|
1039
|
+
}
|
|
1049
1040
|
const configFile = "secrets-config.json";
|
|
1050
1041
|
if (!fs5.existsSync(configFile)) {
|
|
1051
|
-
console.error("
|
|
1052
|
-
|
|
1042
|
+
console.error("Error: Configuration file not found.");
|
|
1043
|
+
console.error('Run "learn-secrets init" first to create config.');
|
|
1044
|
+
process.exit(1);
|
|
1053
1045
|
}
|
|
1054
1046
|
const config = JSON.parse(fs5.readFileSync(configFile, "utf8"));
|
|
1055
|
-
|
|
1047
|
+
if (!config.project) {
|
|
1048
|
+
console.error("Error: No project ID in config.");
|
|
1049
|
+
console.error('Run "learn-secrets config --project <appid>" to set project.');
|
|
1050
|
+
process.exit(1);
|
|
1051
|
+
}
|
|
1052
|
+
if (!config.origins || config.origins.length === 0) {
|
|
1053
|
+
console.error("Error: No origins in config.");
|
|
1054
|
+
console.error('Run "learn-secrets config --origins domain1.com,domain2.com" to set origins.');
|
|
1055
|
+
process.exit(1);
|
|
1056
|
+
}
|
|
1057
|
+
console.log(`
|
|
1058
|
+
Deploying secrets to project: ${config.project}`);
|
|
1059
|
+
console.log(`Origins: ${config.origins.join(", ")}
|
|
1060
|
+
`);
|
|
1061
|
+
const envFile = options.env || ".env";
|
|
1062
|
+
let secrets;
|
|
1063
|
+
let envVars = [];
|
|
1064
|
+
try {
|
|
1065
|
+
console.log(`Reading ${envFile}...`);
|
|
1066
|
+
envVars = parseEnvFile(envFile);
|
|
1067
|
+
secrets = convertAllEnvVarsToSecrets(envVars);
|
|
1068
|
+
if (secrets.length === 0) {
|
|
1069
|
+
console.log("\nNo environment variables found in .env file.");
|
|
1070
|
+
console.log("Make sure your .env file is not empty.");
|
|
1071
|
+
process.exit(0);
|
|
1072
|
+
}
|
|
1073
|
+
console.log("\n" + summarizeDetectedSecrets(secrets));
|
|
1074
|
+
console.log("");
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
console.error(`Error reading ${envFile}:`, error.message);
|
|
1077
|
+
process.exit(1);
|
|
1078
|
+
}
|
|
1079
|
+
if (!options.yes) {
|
|
1080
|
+
const readline = await import("readline");
|
|
1081
|
+
const rl = readline.createInterface({
|
|
1082
|
+
input: process.stdin,
|
|
1083
|
+
output: process.stdout
|
|
1084
|
+
});
|
|
1085
|
+
const answer = await new Promise((resolve2) => {
|
|
1086
|
+
rl.question("Deploy these secrets? (y/N): ", resolve2);
|
|
1087
|
+
});
|
|
1088
|
+
rl.close();
|
|
1089
|
+
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
1090
|
+
console.log("Cancelled.");
|
|
1091
|
+
process.exit(0);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
console.log("\nUploading secrets...");
|
|
1095
|
+
const mgmt = new SecretsManagement({
|
|
1096
|
+
appId: config.project,
|
|
1097
|
+
baseUrl: options.baseUrl
|
|
1098
|
+
});
|
|
1099
|
+
try {
|
|
1100
|
+
const result = await mgmt.importSecrets({
|
|
1101
|
+
version: "1.0",
|
|
1102
|
+
origins: config.origins,
|
|
1103
|
+
secrets
|
|
1104
|
+
});
|
|
1105
|
+
console.log("\nSuccess!");
|
|
1106
|
+
console.log(` Created: ${result.results.created} secrets`);
|
|
1107
|
+
console.log(` Updated: ${result.results.updated} secrets`);
|
|
1108
|
+
if (result.results.failed > 0) {
|
|
1109
|
+
console.log(` Failed: ${result.results.failed} secrets`);
|
|
1110
|
+
for (const failure of result.results.details.failed) {
|
|
1111
|
+
console.log(` - ${failure.name}: ${failure.error}`);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
const dashboardUrl = options.baseUrl || "https://cloudprototype.org";
|
|
1115
|
+
console.log(`
|
|
1116
|
+
Secrets deployed! View them at: ${dashboardUrl}/dashboard/secrets/api_keys`);
|
|
1117
|
+
console.log("\n--- Cloudflare Worker Deployment ---");
|
|
1118
|
+
let cloudflareConnected = false;
|
|
1119
|
+
try {
|
|
1120
|
+
const statusResponse = await fetch(`${dashboardUrl}/api/cloudflare/status`, {
|
|
1121
|
+
credentials: "include"
|
|
1122
|
+
});
|
|
1123
|
+
cloudflareConnected = statusResponse.ok;
|
|
1124
|
+
} catch {
|
|
1125
|
+
cloudflareConnected = false;
|
|
1126
|
+
}
|
|
1127
|
+
if (!cloudflareConnected) {
|
|
1128
|
+
console.log("Cloudflare account not connected.");
|
|
1129
|
+
console.log("\nTo deploy Worker, we need your Cloudflare API token.");
|
|
1130
|
+
console.log("Required permissions:");
|
|
1131
|
+
console.log(" - Workers Scripts: Edit");
|
|
1132
|
+
console.log(" - Workers Routes: Edit");
|
|
1133
|
+
console.log(" - Account Settings: Read");
|
|
1134
|
+
console.log("\nCreate token at: https://dash.cloudflare.com/profile/api-tokens");
|
|
1135
|
+
const readline = await import("readline");
|
|
1136
|
+
const rl = readline.createInterface({
|
|
1137
|
+
input: process.stdin,
|
|
1138
|
+
output: process.stdout
|
|
1139
|
+
});
|
|
1140
|
+
const apiToken = await new Promise((resolve2) => {
|
|
1141
|
+
rl.question("\nCloudflare API Token (or press Enter to skip): ", (answer) => {
|
|
1142
|
+
rl.close();
|
|
1143
|
+
resolve2(answer.trim());
|
|
1144
|
+
});
|
|
1145
|
+
});
|
|
1146
|
+
if (apiToken) {
|
|
1147
|
+
console.log("\nValidating token...");
|
|
1148
|
+
const result2 = await connectCloudflare(apiToken, options.baseUrl);
|
|
1149
|
+
if (result2.success) {
|
|
1150
|
+
console.log("Cloudflare account connected");
|
|
1151
|
+
console.log(` Account: ${result2.accountName}`);
|
|
1152
|
+
cloudflareConnected = true;
|
|
1153
|
+
} else {
|
|
1154
|
+
console.log(`
|
|
1155
|
+
Error: Failed to connect Cloudflare: ${result2.error}`);
|
|
1156
|
+
console.log("Worker deployment skipped.");
|
|
1157
|
+
}
|
|
1158
|
+
} else {
|
|
1159
|
+
console.log("\nSkipped Cloudflare connection. Worker deployment skipped.");
|
|
1160
|
+
}
|
|
1161
|
+
} else {
|
|
1162
|
+
console.log("Cloudflare account is connected.");
|
|
1163
|
+
}
|
|
1164
|
+
if (cloudflareConnected) {
|
|
1165
|
+
const readline = await import("readline");
|
|
1166
|
+
const rl = readline.createInterface({
|
|
1167
|
+
input: process.stdin,
|
|
1168
|
+
output: process.stdout
|
|
1169
|
+
});
|
|
1170
|
+
const shouldProvision = await new Promise((resolve2) => {
|
|
1171
|
+
rl.question("\nDeploy/update Worker to Cloudflare? (y/N): ", (answer) => {
|
|
1172
|
+
rl.close();
|
|
1173
|
+
resolve2(answer.trim());
|
|
1174
|
+
});
|
|
1175
|
+
});
|
|
1176
|
+
if (shouldProvision.toLowerCase() === "y" || shouldProvision.toLowerCase() === "yes") {
|
|
1177
|
+
console.log("\nDeploying Worker to Cloudflare...");
|
|
1178
|
+
const workerName = `learn-secrets-${config.project.toLowerCase()}`;
|
|
1179
|
+
const provisionResult = await provisionWorker(
|
|
1180
|
+
workerName,
|
|
1181
|
+
config.origins,
|
|
1182
|
+
envVars,
|
|
1183
|
+
options.baseUrl
|
|
1184
|
+
);
|
|
1185
|
+
if (provisionResult.success) {
|
|
1186
|
+
console.log("\nWorker deployed successfully");
|
|
1187
|
+
console.log(` Name: ${provisionResult.workerName}`);
|
|
1188
|
+
console.log(` URL: ${provisionResult.workerUrl}`);
|
|
1189
|
+
config.workerUrl = provisionResult.workerUrl;
|
|
1190
|
+
fs5.writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
1191
|
+
console.log("\nNext steps:");
|
|
1192
|
+
console.log(` 1. Test Worker: curl ${provisionResult.workerUrl}/health`);
|
|
1193
|
+
console.log(` 2. Update your frontend to use Worker URL`);
|
|
1194
|
+
console.log(` 3. Test origin validation from your site`);
|
|
1195
|
+
} else {
|
|
1196
|
+
console.log(`
|
|
1197
|
+
Error: Worker deployment failed: ${provisionResult.error}`);
|
|
1198
|
+
}
|
|
1199
|
+
} else {
|
|
1200
|
+
console.log("\nSkipped Worker deployment.");
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
console.error("\nDeploy failed:", error.message);
|
|
1205
|
+
if (error.status === 401) {
|
|
1206
|
+
console.error('Your session may have expired. Try running "learn-secrets login" again.');
|
|
1207
|
+
}
|
|
1208
|
+
if (error.status === 404) {
|
|
1209
|
+
console.error(`Project "${config.project}" not found. Check your project ID.`);
|
|
1210
|
+
}
|
|
1211
|
+
process.exit(1);
|
|
1212
|
+
}
|
|
1056
1213
|
}
|
|
1057
1214
|
|
|
1058
1215
|
// src/cli/index.ts
|
|
1059
1216
|
var program = new Command();
|
|
1060
|
-
program.name("learn-secrets").description("CLI tool for managing API secrets
|
|
1061
|
-
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) => {
|
|
1062
|
-
await setupCommand(options);
|
|
1063
|
-
});
|
|
1217
|
+
program.name("learn-secrets").description("CLI tool for managing API secrets with Cloudflare Worker deployment").version("1.8.0");
|
|
1064
1218
|
program.command("login").description("Authenticate with Learn Secrets via browser").option("--base-url <url>", "Custom base URL for API").action(async (options) => {
|
|
1065
1219
|
await loginCommand(options);
|
|
1066
1220
|
});
|
|
@@ -1070,8 +1224,8 @@ program.command("init").description("Initialize secrets for a new site").require
|
|
|
1070
1224
|
program.command("config").description("View or update project configuration").option("-p, --project <appid>", "Change project ID").option("--origins <origins>", "Comma separated origins").action(async (options) => {
|
|
1071
1225
|
await configCommand(options);
|
|
1072
1226
|
});
|
|
1073
|
-
program.command("deploy").description("Deploy secrets
|
|
1074
|
-
await deployCommand();
|
|
1227
|
+
program.command("deploy").description("Deploy secrets from .env to dashboard").option("-e, --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) => {
|
|
1228
|
+
await deployCommand(options);
|
|
1075
1229
|
});
|
|
1076
1230
|
program.parse(process.argv);
|
|
1077
1231
|
if (!process.argv.slice(2).length) {
|
package/dist/index.d.mts
CHANGED
|
@@ -18,14 +18,14 @@ interface SecretsSDKOptions {
|
|
|
18
18
|
timeout?: number;
|
|
19
19
|
retryOn429?: boolean;
|
|
20
20
|
}
|
|
21
|
-
interface ProxyRequest {
|
|
21
|
+
interface ProxyRequest$1 {
|
|
22
22
|
keyName: string;
|
|
23
23
|
endpoint: string;
|
|
24
24
|
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
25
25
|
body?: any;
|
|
26
26
|
headers?: Record<string, string>;
|
|
27
27
|
}
|
|
28
|
-
interface ProxyResponse<T = any> {
|
|
28
|
+
interface ProxyResponse$1<T = any> {
|
|
29
29
|
success: boolean;
|
|
30
30
|
status: number;
|
|
31
31
|
data: T;
|
|
@@ -157,7 +157,7 @@ declare class SecretsSDK {
|
|
|
157
157
|
* @param options - Request options (method, body, headers)
|
|
158
158
|
* @returns Promise with the API response
|
|
159
159
|
*/
|
|
160
|
-
call<T = any>(keyName: string, endpoint: string, options?: Omit<ProxyRequest, 'keyName' | 'endpoint'>): Promise<T>;
|
|
160
|
+
call<T = any>(keyName: string, endpoint: string, options?: Omit<ProxyRequest$1, 'keyName' | 'endpoint'>): Promise<T>;
|
|
161
161
|
/**
|
|
162
162
|
* Make a GET request
|
|
163
163
|
*/
|
|
@@ -367,6 +367,116 @@ declare class SecretsManagement {
|
|
|
367
367
|
}>;
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Worker Client - Frontend SDK for calling Learn Secrets Worker
|
|
372
|
+
*
|
|
373
|
+
* Use this client to call your deployed Cloudflare Worker from static sites
|
|
374
|
+
*/
|
|
375
|
+
interface WorkerClientOptions {
|
|
376
|
+
workerUrl: string;
|
|
377
|
+
}
|
|
378
|
+
interface ProxyRequest {
|
|
379
|
+
secret: string;
|
|
380
|
+
url: string;
|
|
381
|
+
method?: string;
|
|
382
|
+
headers?: Record<string, string>;
|
|
383
|
+
body?: any;
|
|
384
|
+
authHeader?: string;
|
|
385
|
+
authPrefix?: string;
|
|
386
|
+
}
|
|
387
|
+
interface ProxyResponse<T = any> {
|
|
388
|
+
success: boolean;
|
|
389
|
+
status: number;
|
|
390
|
+
data: T;
|
|
391
|
+
}
|
|
392
|
+
interface PublicConfig {
|
|
393
|
+
[key: string]: string;
|
|
394
|
+
}
|
|
395
|
+
interface AppleJwtRequest {
|
|
396
|
+
teamId?: string;
|
|
397
|
+
keyId?: string;
|
|
398
|
+
privateKey: string;
|
|
399
|
+
}
|
|
400
|
+
interface OAuthExchangeRequest {
|
|
401
|
+
provider: string;
|
|
402
|
+
code: string;
|
|
403
|
+
redirectUri: string;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Worker Client for calling Learn Secrets Worker
|
|
407
|
+
*
|
|
408
|
+
* SECURITY:
|
|
409
|
+
* - All requests include Origin header (automatically added by browser)
|
|
410
|
+
* - Worker validates origin before processing
|
|
411
|
+
* - Secrets never exposed to frontend
|
|
412
|
+
* - Only public config returned to frontend
|
|
413
|
+
*
|
|
414
|
+
* Example:
|
|
415
|
+
* ```typescript
|
|
416
|
+
* const client = new WorkerClient({
|
|
417
|
+
* workerUrl: 'https://learn-secrets-myapp.myaccount.workers.dev'
|
|
418
|
+
* });
|
|
419
|
+
*
|
|
420
|
+
* // Get public config
|
|
421
|
+
* const config = await client.getConfig();
|
|
422
|
+
*
|
|
423
|
+
* // Make API call with secret
|
|
424
|
+
* const response = await client.proxy({
|
|
425
|
+
* secret: 'openai-api-key',
|
|
426
|
+
* url: 'https://api.openai.com/v1/chat/completions',
|
|
427
|
+
* method: 'POST',
|
|
428
|
+
* body: { model: 'gpt-4', messages: [...] }
|
|
429
|
+
* });
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
declare class WorkerClient {
|
|
433
|
+
private workerUrl;
|
|
434
|
+
constructor(options: WorkerClientOptions);
|
|
435
|
+
/**
|
|
436
|
+
* Get public configuration
|
|
437
|
+
*
|
|
438
|
+
* Returns only PUBLIC_* environment variables from Worker
|
|
439
|
+
* Safe for frontend use
|
|
440
|
+
*/
|
|
441
|
+
getConfig(): Promise<PublicConfig>;
|
|
442
|
+
/**
|
|
443
|
+
* Proxy API request with secret injection
|
|
444
|
+
*
|
|
445
|
+
* Makes API call through Worker which injects secret server-side
|
|
446
|
+
* Secret never reaches browser
|
|
447
|
+
*
|
|
448
|
+
* @param request - Proxy request configuration
|
|
449
|
+
* @returns API response data
|
|
450
|
+
*/
|
|
451
|
+
proxy<T = any>(request: ProxyRequest): Promise<ProxyResponse<T>>;
|
|
452
|
+
/**
|
|
453
|
+
* Generate Apple Music JWT
|
|
454
|
+
*
|
|
455
|
+
* Private key stays in Worker, only JWT returned to frontend
|
|
456
|
+
*
|
|
457
|
+
* @param request - Apple JWT request with secret name
|
|
458
|
+
* @returns JWT token
|
|
459
|
+
*/
|
|
460
|
+
generateAppleJwt(request: AppleJwtRequest): Promise<string>;
|
|
461
|
+
/**
|
|
462
|
+
* Exchange OAuth authorization code for access token
|
|
463
|
+
*
|
|
464
|
+
* Client secret stays in Worker, tokens returned to frontend
|
|
465
|
+
*
|
|
466
|
+
* @param request - OAuth exchange request
|
|
467
|
+
* @returns OAuth token response
|
|
468
|
+
*/
|
|
469
|
+
exchangeOAuthCode(request: OAuthExchangeRequest): Promise<any>;
|
|
470
|
+
/**
|
|
471
|
+
* Health check
|
|
472
|
+
*/
|
|
473
|
+
health(): Promise<{
|
|
474
|
+
status: string;
|
|
475
|
+
version: string;
|
|
476
|
+
timestamp: string;
|
|
477
|
+
}>;
|
|
478
|
+
}
|
|
479
|
+
|
|
370
480
|
/**
|
|
371
481
|
* Resolve environment variable references in a string
|
|
372
482
|
* Supports ${VAR} and $VAR syntax
|
|
@@ -429,4 +539,4 @@ declare function deleteSDKCredentials(sdkName: SDKName): void;
|
|
|
429
539
|
*/
|
|
430
540
|
declare function hasSDKCredentials(sdkName: SDKName): boolean;
|
|
431
541
|
|
|
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 };
|
|
542
|
+
export { type CLICredentials, InvalidTokenError, type MachineInfo, type MaskedSecret, OriginMismatchError, type ProxyRequest$1 as ProxyRequest, type ProxyResponse$1 as ProxyResponse, RateLimitError, type RateLimitInfo, type SDKCredentialsStore, type SDKName, type SecretConfig, SecretsManagement, type SecretsManagementOptions, SecretsSDK, SecretsSDKError, type SecretsSDKOptions, type SyncOptions, type SyncResult, WorkerClient, deleteSDKCredentials, getMachineId, hasSDKCredentials, loadSecretsConfig, readAllCredentials, readSDKCredentials, resolveEnvInSecret, resolveEnvInSecrets, resolveEnvString, writeAllCredentials, writeSDKCredentials };
|
package/dist/index.d.ts
CHANGED
|
@@ -18,14 +18,14 @@ interface SecretsSDKOptions {
|
|
|
18
18
|
timeout?: number;
|
|
19
19
|
retryOn429?: boolean;
|
|
20
20
|
}
|
|
21
|
-
interface ProxyRequest {
|
|
21
|
+
interface ProxyRequest$1 {
|
|
22
22
|
keyName: string;
|
|
23
23
|
endpoint: string;
|
|
24
24
|
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
25
25
|
body?: any;
|
|
26
26
|
headers?: Record<string, string>;
|
|
27
27
|
}
|
|
28
|
-
interface ProxyResponse<T = any> {
|
|
28
|
+
interface ProxyResponse$1<T = any> {
|
|
29
29
|
success: boolean;
|
|
30
30
|
status: number;
|
|
31
31
|
data: T;
|
|
@@ -157,7 +157,7 @@ declare class SecretsSDK {
|
|
|
157
157
|
* @param options - Request options (method, body, headers)
|
|
158
158
|
* @returns Promise with the API response
|
|
159
159
|
*/
|
|
160
|
-
call<T = any>(keyName: string, endpoint: string, options?: Omit<ProxyRequest, 'keyName' | 'endpoint'>): Promise<T>;
|
|
160
|
+
call<T = any>(keyName: string, endpoint: string, options?: Omit<ProxyRequest$1, 'keyName' | 'endpoint'>): Promise<T>;
|
|
161
161
|
/**
|
|
162
162
|
* Make a GET request
|
|
163
163
|
*/
|
|
@@ -367,6 +367,116 @@ declare class SecretsManagement {
|
|
|
367
367
|
}>;
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Worker Client - Frontend SDK for calling Learn Secrets Worker
|
|
372
|
+
*
|
|
373
|
+
* Use this client to call your deployed Cloudflare Worker from static sites
|
|
374
|
+
*/
|
|
375
|
+
interface WorkerClientOptions {
|
|
376
|
+
workerUrl: string;
|
|
377
|
+
}
|
|
378
|
+
interface ProxyRequest {
|
|
379
|
+
secret: string;
|
|
380
|
+
url: string;
|
|
381
|
+
method?: string;
|
|
382
|
+
headers?: Record<string, string>;
|
|
383
|
+
body?: any;
|
|
384
|
+
authHeader?: string;
|
|
385
|
+
authPrefix?: string;
|
|
386
|
+
}
|
|
387
|
+
interface ProxyResponse<T = any> {
|
|
388
|
+
success: boolean;
|
|
389
|
+
status: number;
|
|
390
|
+
data: T;
|
|
391
|
+
}
|
|
392
|
+
interface PublicConfig {
|
|
393
|
+
[key: string]: string;
|
|
394
|
+
}
|
|
395
|
+
interface AppleJwtRequest {
|
|
396
|
+
teamId?: string;
|
|
397
|
+
keyId?: string;
|
|
398
|
+
privateKey: string;
|
|
399
|
+
}
|
|
400
|
+
interface OAuthExchangeRequest {
|
|
401
|
+
provider: string;
|
|
402
|
+
code: string;
|
|
403
|
+
redirectUri: string;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Worker Client for calling Learn Secrets Worker
|
|
407
|
+
*
|
|
408
|
+
* SECURITY:
|
|
409
|
+
* - All requests include Origin header (automatically added by browser)
|
|
410
|
+
* - Worker validates origin before processing
|
|
411
|
+
* - Secrets never exposed to frontend
|
|
412
|
+
* - Only public config returned to frontend
|
|
413
|
+
*
|
|
414
|
+
* Example:
|
|
415
|
+
* ```typescript
|
|
416
|
+
* const client = new WorkerClient({
|
|
417
|
+
* workerUrl: 'https://learn-secrets-myapp.myaccount.workers.dev'
|
|
418
|
+
* });
|
|
419
|
+
*
|
|
420
|
+
* // Get public config
|
|
421
|
+
* const config = await client.getConfig();
|
|
422
|
+
*
|
|
423
|
+
* // Make API call with secret
|
|
424
|
+
* const response = await client.proxy({
|
|
425
|
+
* secret: 'openai-api-key',
|
|
426
|
+
* url: 'https://api.openai.com/v1/chat/completions',
|
|
427
|
+
* method: 'POST',
|
|
428
|
+
* body: { model: 'gpt-4', messages: [...] }
|
|
429
|
+
* });
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
declare class WorkerClient {
|
|
433
|
+
private workerUrl;
|
|
434
|
+
constructor(options: WorkerClientOptions);
|
|
435
|
+
/**
|
|
436
|
+
* Get public configuration
|
|
437
|
+
*
|
|
438
|
+
* Returns only PUBLIC_* environment variables from Worker
|
|
439
|
+
* Safe for frontend use
|
|
440
|
+
*/
|
|
441
|
+
getConfig(): Promise<PublicConfig>;
|
|
442
|
+
/**
|
|
443
|
+
* Proxy API request with secret injection
|
|
444
|
+
*
|
|
445
|
+
* Makes API call through Worker which injects secret server-side
|
|
446
|
+
* Secret never reaches browser
|
|
447
|
+
*
|
|
448
|
+
* @param request - Proxy request configuration
|
|
449
|
+
* @returns API response data
|
|
450
|
+
*/
|
|
451
|
+
proxy<T = any>(request: ProxyRequest): Promise<ProxyResponse<T>>;
|
|
452
|
+
/**
|
|
453
|
+
* Generate Apple Music JWT
|
|
454
|
+
*
|
|
455
|
+
* Private key stays in Worker, only JWT returned to frontend
|
|
456
|
+
*
|
|
457
|
+
* @param request - Apple JWT request with secret name
|
|
458
|
+
* @returns JWT token
|
|
459
|
+
*/
|
|
460
|
+
generateAppleJwt(request: AppleJwtRequest): Promise<string>;
|
|
461
|
+
/**
|
|
462
|
+
* Exchange OAuth authorization code for access token
|
|
463
|
+
*
|
|
464
|
+
* Client secret stays in Worker, tokens returned to frontend
|
|
465
|
+
*
|
|
466
|
+
* @param request - OAuth exchange request
|
|
467
|
+
* @returns OAuth token response
|
|
468
|
+
*/
|
|
469
|
+
exchangeOAuthCode(request: OAuthExchangeRequest): Promise<any>;
|
|
470
|
+
/**
|
|
471
|
+
* Health check
|
|
472
|
+
*/
|
|
473
|
+
health(): Promise<{
|
|
474
|
+
status: string;
|
|
475
|
+
version: string;
|
|
476
|
+
timestamp: string;
|
|
477
|
+
}>;
|
|
478
|
+
}
|
|
479
|
+
|
|
370
480
|
/**
|
|
371
481
|
* Resolve environment variable references in a string
|
|
372
482
|
* Supports ${VAR} and $VAR syntax
|
|
@@ -429,4 +539,4 @@ declare function deleteSDKCredentials(sdkName: SDKName): void;
|
|
|
429
539
|
*/
|
|
430
540
|
declare function hasSDKCredentials(sdkName: SDKName): boolean;
|
|
431
541
|
|
|
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 };
|
|
542
|
+
export { type CLICredentials, InvalidTokenError, type MachineInfo, type MaskedSecret, OriginMismatchError, type ProxyRequest$1 as ProxyRequest, type ProxyResponse$1 as ProxyResponse, RateLimitError, type RateLimitInfo, type SDKCredentialsStore, type SDKName, type SecretConfig, SecretsManagement, type SecretsManagementOptions, SecretsSDK, SecretsSDKError, type SecretsSDKOptions, type SyncOptions, type SyncResult, WorkerClient, deleteSDKCredentials, getMachineId, hasSDKCredentials, loadSecretsConfig, readAllCredentials, readSDKCredentials, resolveEnvInSecret, resolveEnvInSecrets, resolveEnvString, writeAllCredentials, writeSDKCredentials };
|
package/dist/index.global.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
"use strict";var SecretsSDK=(()=>{var
|
|
2
|
-
`).map(s=>s.trim()).find(s=>s&&s!=="UUID");if(
|
|
3
|
-
`);let
|
|
1
|
+
"use strict";var SecretsSDK=(()=>{var Z=Object.create;var T=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var ee=Object.getPrototypeOf,te=Object.prototype.hasOwnProperty;var l=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+r+'" is not supported')});var re=(r,e)=>()=>(r&&(e=r(r=0)),e);var z=(r,e)=>{for(var t in e)T(r,t,{get:e[t],enumerable:!0})},F=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Y(e))!te.call(r,n)&&n!==t&&T(r,n,{get:()=>e[n],enumerable:!(s=Q(e,n))||s.enumerable});return r};var y=(r,e,t)=>(t=r!=null?Z(ee(r)):{},F(e||!r||!r.__esModule?T(t,"default",{value:r,enumerable:!0}):t,r)),se=r=>F(T({},"__esModule",{value:!0}),r);var X={};z(X,{loadSecretsConfig:()=>N,resolveEnvInSecret:()=>E,resolveEnvInSecrets:()=>$,resolveEnvString:()=>p});function p(r){return r.replace(/\$\{([A-Z0-9_]+)\}|\$([A-Z0-9_]+)/g,(e,t,s)=>{let n=t||s,i=process.env[n];if(i===void 0)throw new Error(`Environment variable ${n} is not defined`);return i})}function E(r){let e={name:p(r.name),provider:p(r.provider),api_key:p(r.api_key)};return r.base_url&&(e.base_url=p(r.base_url)),r.auth_header&&(e.auth_header=p(r.auth_header)),r.auth_prefix&&(e.auth_prefix=p(r.auth_prefix)),e}function $(r){return r.map(E)}function N(r){let e=l("fs"),s=l("path").resolve(r);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=re(()=>{"use strict"});var ce={};z(ce,{InvalidTokenError:()=>b,OriginMismatchError:()=>S,RateLimitError:()=>x,SecretsManagement:()=>j,SecretsSDK:()=>_,SecretsSDKError:()=>o,WorkerClient:()=>A,deleteSDKCredentials:()=>H,getMachineId:()=>O,hasSDKCredentials:()=>W,loadSecretsConfig:()=>N,readAllCredentials:()=>v,readSDKCredentials:()=>k,resolveEnvInSecret:()=>E,resolveEnvInSecrets:()=>$,resolveEnvString:()=>p,writeAllCredentials:()=>R,writeSDKCredentials:()=>D});var o=class extends Error{constructor(t,s,n){super(t);this.status=s;this.response=n;this.name="SecretsSDKError"}},S=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",t=60,s=0,n=100){super(e,429),this.name="RateLimitError",this.retryAfter=t,this.remaining=s,this.limit=n}},b=class extends o{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://cloudprototype.org",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"),s=e.get("X-RateLimit-Remaining"),n=e.get("X-RateLimit-Reset");t&&s&&n&&(this.rateLimitInfo={limit:parseInt(t),remaining:parseInt(s),reset:parseInt(n)})}sleep(e){return new Promise(t=>setTimeout(t,e))}async fetchWithTimeout(e,t,s){let n=new AbortController,i=setTimeout(()=>n.abort(),s);try{return await fetch(e,{...t,signal:n.signal})}finally{clearTimeout(i)}}async call(e,t,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`,m={"Content-Type":"application/json"};!this.zeroConfigMode&&this.token&&(m.Authorization=`Bearer ${this.token}`);let c=await this.fetchWithTimeout(a,{method:"POST",headers:m,body:JSON.stringify({keyName:e,endpoint:t,method:s.method||"GET",body:s.body,headers:s.headers})},this.timeout);this.parseRateLimitHeaders(c.headers);let w=await c.json();if(!c.ok){let h=w.data?.message||w?.message||`Request failed with status ${c.status}`;if(c.status===403&&h.includes("Origin"))throw new S(h);if(c.status===401)throw new b(h);if(c.status===429){let I=this.rateLimitInfo?this.rateLimitInfo.reset-Math.floor(Date.now()/1e3):60,u=new x(h,I,this.rateLimitInfo?.remaining||0,this.rateLimitInfo?.limit||100);if(this.retryOn429&&n<i){n++;let f=Math.min(1e3*Math.pow(2,n-1),8e3);await this.sleep(f);continue}throw u}throw new o(h,c.status,w)}return w.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,t,s){return this.call(e,t,{method:"GET",headers:s})}async post(e,t,s,n){return this.call(e,t,{method:"POST",body:s,headers:n})}async put(e,t,s,n){return this.call(e,t,{method:"PUT",body:s,headers:n})}async delete(e,t,s){return this.call(e,t,{method:"DELETE",headers:s})}async patch(e,t,s,n){return this.call(e,t,{method:"PATCH",body:s,headers:n})}setAppId(e){this.appId=e}async secretsRequest(e,t){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:t?JSON.stringify(t):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,t){return this.secretsRequest("sync",{secrets:e,provider:t})}};var d=y(l("fs")),P=y(l("path")),L=y(l("os"));function U(){let r=L.homedir(),e=P.join(r,".learn");return P.join(e,"credentials.json")}function ne(){let r=L.homedir(),e=P.join(r,".learn");d.existsSync(e)||d.mkdirSync(e,{recursive:!0,mode:448})}function v(){try{let r=U();if(!d.existsSync(r))return null;let e=d.readFileSync(r,"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 R(r){ne();let e=U(),t=JSON.stringify(r,null,2);d.writeFileSync(e,t,{mode:384})}function k(r){let e=v();return e&&e[r]||null}function D(r,e){let t=v()||{};t[r]=e,R(t)}function H(r){let e=v();e&&(delete e[r],Object.keys(e).length===0?K():R(e))}function M(){return k("learn-secrets-sdk")}function K(){try{let r=U();d.existsSync(r)&&d.unlinkSync(r)}catch{}}function W(r){let e=k(r);return e!==null&&!!e.access_token}function G(){let r=M();return r?r.expires_at?new Date(r.expires_at)>new Date:!0:!1}var B=l("child_process"),V=l("util"),q=l("crypto"),g=y(l("os")),C=(0,V.promisify)(B.exec);async function O(){let r=process.platform;try{let e;if(r==="darwin")e=await ie();else if(r==="linux")e=await oe();else if(r==="win32")e=await ae();else throw new Error(`Unsupported platform: ${r}`);let t=(0,q.createHash)("sha256").update(e).digest("hex").substring(0,32),s=JSON.stringify({machineId:t,cpus:g.cpus().length,arch:g.arch(),platform:g.platform(),homedir:g.homedir()}),n=(0,q.createHash)("sha256").update(s).digest("hex");return{machineId:t,fingerprint:n}}catch(e){throw new Error(`Failed to get machine ID: ${e.message}`)}}async function ie(){try{let{stdout:r}=await C(`ioreg -l | grep IOPlatformSerialNumber | awk '{print $4}' | sed 's/"//g'`),{stdout:e}=await C("hostname"),t=r.trim(),s=e.trim();if(t&&s)return`${t}-${s}`;let{stdout:n}=await C(`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 oe(){try{let{stdout:r}=await C("cat /etc/machine-id 2>/dev/null || cat /var/lib/dbus/machine-id 2>/dev/null"),e=r.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 ae(){try{let{stdout:r}=await C("wmic csproduct get uuid"),t=r.split(`
|
|
2
|
+
`).map(s=>s.trim()).find(s=>s&&s!=="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 j=class{constructor(e){this.appId=e.appId,this.baseUrl=e.baseUrl||"https://cloudprototype.org",this.timeout=e.timeout||3e4}async openBrowser(e){let{exec:t}=await import("child_process"),{promisify:s}=await import("util"),n=s(t),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(t=>setTimeout(t,e))}async login(){let e="learn-secrets-sdk";try{console.log(`Authenticating with Learn (${e})...
|
|
3
|
+
`);let t=await O(),s=k(e);if(s&&s.access_token){let u=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(u.ok){let f=await u.json();if(f.authorized)return console.log("This machine is already authorized."),console.log(`Logged in as: ${f.user_email||f.user_id}`),{success:!0,user:f.user_email||f.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
4
|
Log in on ${this.baseUrl}`),console.log("Login at:"),console.log(i.verification_uri_complete),console.log(`
|
|
5
5
|
Press ENTER to open in the browser...`);let m=(await import("readline")).createInterface({input:process.stdin,output:process.stdout});await new Promise(u=>{m.question("",()=>{m.close(),u()})}),console.log(`Opening browser...
|
|
6
|
-
`),await this.openBrowser(i.verification_uri_complete);let c=null,w=i.interval*1e3,h=Math.ceil(i.expires_in/i.interval),
|
|
6
|
+
`),await this.openBrowser(i.verification_uri_complete);let c=null,w=i.interval*1e3,h=Math.ceil(i.expires_in/i.interval),I=0;for(;I<h;){await this.sleep(w),I++;let u=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:i.device_code})});if(u.status===202){process.stdout.write(".");continue}if(u.ok){c=await u.json();break}}if(!c)throw new o("Device authorization timeout",408);return console.log(`
|
|
7
7
|
|
|
8
|
-
Device authorized! Retrieving access token...`),
|
|
8
|
+
Device authorized! Retrieving access token...`),D(e,{access_token:c.access_token,refresh_token:null,expires_at:null,user_id:c.user_id,machine_id:t.machineId,sdk_name:e,authorized_at:new Date().toISOString()}),console.log(`
|
|
9
9
|
Authentication successful!`),console.log(`Logged in as: ${c.user_email||c.user_id}`),console.log(`This machine is now permanently authorized.
|
|
10
|
-
`),{success:!0,user:c.user_email||c.user_id}}catch(
|
|
10
|
+
`),{success:!0,user:c.user_email||c.user_id}}catch(t){throw t instanceof o?t:new o(t.message||"Login failed",t.status||500)}}async isAuthenticated(){return G()}logout(){K(),console.log("Logged out successfully")}getAccessToken(){let e=M();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,t,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}${t}`,i);if(!a.ok){let m=await a.json().catch(()=>({}));throw new o(m.message||`Request failed: ${a.statusText}`,a.status,m)}return a.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 Promise.resolve().then(()=>(J(),X)),s=t(e);return this.importSecrets(s)}};var A=class{constructor(e){this.workerUrl=e.workerUrl.replace(/\/$/,"")}async getConfig(){let e=await fetch(`${this.workerUrl}/config`,{method:"GET",credentials:"include"});if(!e.ok)throw new Error(`Failed to get config: ${e.status} ${e.statusText}`);return(await e.json()).public||{}}async proxy(e){let t=await fetch(`${this.workerUrl}/proxy`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(e)});if(!t.ok){let s=await t.json().catch(()=>({error:"Unknown error"}));throw new Error(s.error||`Proxy request failed: ${t.status}`)}return t.json()}async generateAppleJwt(e){let t=await fetch(`${this.workerUrl}/auth/apple-jwt`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(e)});if(!t.ok){let n=await t.json().catch(()=>({error:"Unknown error"}));throw new Error(n.error||`Apple JWT generation failed: ${t.status}`)}return(await t.json()).token}async exchangeOAuthCode(e){let t=await fetch(`${this.workerUrl}/auth/oauth`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(e)});if(!t.ok){let s=await t.json().catch(()=>({error:"Unknown error"}));throw new Error(s.error||`OAuth exchange failed: ${t.status}`)}return t.json()}async health(){let e=await fetch(`${this.workerUrl}/health`,{method:"GET"});if(!e.ok)throw new Error(`Health check failed: ${e.status}`);return e.json()}};J();return se(ce);})();
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{a as
|
|
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
|
|
1
|
+
import{a as j,b as A,c as M,d as K}from"./chunk-AS6G7JYX.mjs";var i=class extends Error{constructor(t,r,n){super(t);this.status=r;this.response=n;this.name="SecretsSDKError"}},f=class extends i{constructor(e="Origin not allowed for this token"){super(e,403),this.name="OriginMismatchError"}},y=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}},w=class extends i{constructor(e="Invalid or expired token"){super(e,401),this.name="InvalidTokenError"}};var T=class{constructor(e={}){this.rateLimitInfo=null;this.appId=e.appId||null,this.token=e.token||e.sessionToken||null,this.baseUrl=e.baseUrl||"https://cloudprototype.org",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,c=setTimeout(()=>n.abort(),r);try{return await fetch(e,{...t,signal:n.signal})}finally{clearTimeout(c)}}async call(e,t,r={}){let n=0,c=this.retryOn429?3:0;for(;;)try{let o=this.zeroConfigMode?`${this.baseUrl}/api/sdk/proxy`:`${this.baseUrl}/api/sdk/${this.appId}/proxy`,u={"Content-Type":"application/json"};!this.zeroConfigMode&&this.token&&(u.Authorization=`Bearer ${this.token}`);let a=await this.fetchWithTimeout(o,{method:"POST",headers:u,body:JSON.stringify({keyName:e,endpoint:t,method:r.method||"GET",body:r.body,headers:r.headers})},this.timeout);this.parseRateLimitHeaders(a.headers);let h=await a.json();if(!a.ok){let m=h.data?.message||h?.message||`Request failed with status ${a.status}`;if(a.status===403&&m.includes("Origin"))throw new f(m);if(a.status===401)throw new w(m);if(a.status===429){let b=this.rateLimitInfo?this.rateLimitInfo.reset-Math.floor(Date.now()/1e3):60,l=new y(m,b,this.rateLimitInfo?.remaining||0,this.rateLimitInfo?.limit||100);if(this.retryOn429&&n<c){n++;let g=Math.min(1e3*Math.pow(2,n-1),8e3);await this.sleep(g);continue}throw l}throw new i(m,a.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 d from"fs";import*as k from"path";import*as C from"os";function v(){let s=C.homedir(),e=k.join(s,".learn");return k.join(e,"credentials.json")}function q(){let s=C.homedir(),e=k.join(s,".learn");d.existsSync(e)||d.mkdirSync(e,{recursive:!0,mode:448})}function I(){try{let s=v();if(!d.existsSync(s))return null;let e=d.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){q();let e=v(),t=JSON.stringify(s,null,2);d.writeFileSync(e,t,{mode:384})}function S(s){let e=I();return e&&e[s]||null}function R(s,e){let t=I()||{};t[s]=e,P(t)}function N(s){let e=I();e&&(delete e[s],Object.keys(e).length===0?D():P(e))}function _(){return S("learn-secrets-sdk")}function D(){try{let s=v();d.existsSync(s)&&d.unlinkSync(s)}catch{}}function J(s){let e=S(s);return e!==null&&!!e.access_token}function L(){let s=_();return s?s.expires_at?new Date(s.expires_at)>new Date:!0:!1}import{exec as z}from"child_process";import{promisify as F}from"util";import{createHash as U}from"crypto";import*as p from"os";var x=F(z);async function O(){let s=process.platform;try{let e;if(s==="darwin")e=await H();else if(s==="linux")e=await W();else if(s==="win32")e=await G();else throw new Error(`Unsupported platform: ${s}`);let t=U("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=U("sha256").update(r).digest("hex");return{machineId:t,fingerprint:n}}catch(e){throw new Error(`Failed to get machine ID: ${e.message}`)}}async function H(){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 W(){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 G(){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 E=class{constructor(e){this.appId=e.appId,this.baseUrl=e.baseUrl||"https://cloudprototype.org",this.timeout=e.timeout||3e4}async openBrowser(e){let{exec:t}=await import("child_process"),{promisify:r}=await import("util"),n=r(t),c=process.platform;try{c==="darwin"?await n(`open "${e}"`):c==="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
3
|
`);let t=await O(),r=S(e);if(r&&r.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:t.machineId,sdk_name:e})});if(l.ok){let g=await l.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 c=await n.json();console.log(`
|
|
4
4
|
Log in on ${this.baseUrl}`),console.log("Login at:"),console.log(c.verification_uri_complete),console.log(`
|
|
5
5
|
Press ENTER to open in the browser...`);let u=(await import("readline")).createInterface({input:process.stdin,output:process.stdout});await new Promise(l=>{u.question("",()=>{u.close(),l()})}),console.log(`Opening browser...
|
|
6
|
-
`),await this.openBrowser(c.verification_uri_complete);let a=null,h=c.interval*1e3,m=Math.ceil(c.expires_in/c.interval),
|
|
6
|
+
`),await this.openBrowser(c.verification_uri_complete);let a=null,h=c.interval*1e3,m=Math.ceil(c.expires_in/c.interval),b=0;for(;b<m;){await this.sleep(h),b++;let l=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:c.device_code})});if(l.status===202){process.stdout.write(".");continue}if(l.ok){a=await l.json();break}}if(!a)throw new i("Device authorization timeout",408);return console.log(`
|
|
7
7
|
|
|
8
|
-
Device authorized! Retrieving access token...`),
|
|
8
|
+
Device authorized! Retrieving access token...`),R(e,{access_token:a.access_token,refresh_token:null,expires_at:null,user_id:a.user_id,machine_id:t.machineId,sdk_name:e,authorized_at:new Date().toISOString()}),console.log(`
|
|
9
9
|
Authentication successful!`),console.log(`Logged in as: ${a.user_email||a.user_id}`),console.log(`This machine is now permanently authorized.
|
|
10
|
-
`),{success:!0,user:a.user_email||a.user_id}}catch(t){throw t instanceof i?t:new i(t.message||"Login failed",t.status||500)}}async isAuthenticated(){return
|
|
10
|
+
`),{success:!0,user:a.user_email||a.user_id}}catch(t){throw t instanceof i?t:new i(t.message||"Login failed",t.status||500)}}async isAuthenticated(){return L()}logout(){D(),console.log("Logged out successfully")}getAccessToken(){let e=_();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(),c={method:e,headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`}};r&&(c.body=JSON.stringify(r));let o=await fetch(`${this.baseUrl}${t}`,c);if(!o.ok){let u=await o.json().catch(()=>({}));throw new i(u.message||`Request failed: ${o.statusText}`,o.status,u)}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)}};var $=class{constructor(e){this.workerUrl=e.workerUrl.replace(/\/$/,"")}async getConfig(){let e=await fetch(`${this.workerUrl}/config`,{method:"GET",credentials:"include"});if(!e.ok)throw new Error(`Failed to get config: ${e.status} ${e.statusText}`);return(await e.json()).public||{}}async proxy(e){let t=await fetch(`${this.workerUrl}/proxy`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(e)});if(!t.ok){let r=await t.json().catch(()=>({error:"Unknown error"}));throw new Error(r.error||`Proxy request failed: ${t.status}`)}return t.json()}async generateAppleJwt(e){let t=await fetch(`${this.workerUrl}/auth/apple-jwt`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(e)});if(!t.ok){let n=await t.json().catch(()=>({error:"Unknown error"}));throw new Error(n.error||`Apple JWT generation failed: ${t.status}`)}return(await t.json()).token}async exchangeOAuthCode(e){let t=await fetch(`${this.workerUrl}/auth/oauth`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(e)});if(!t.ok){let r=await t.json().catch(()=>({error:"Unknown error"}));throw new Error(r.error||`OAuth exchange failed: ${t.status}`)}return t.json()}async health(){let e=await fetch(`${this.workerUrl}/health`,{method:"GET"});if(!e.ok)throw new Error(`Health check failed: ${e.status}`);return e.json()}};export{w as InvalidTokenError,f as OriginMismatchError,y as RateLimitError,E as SecretsManagement,T as SecretsSDK,i as SecretsSDKError,$ as WorkerClient,N as deleteSDKCredentials,O as getMachineId,J as hasSDKCredentials,K as loadSecretsConfig,I as readAllCredentials,S as readSDKCredentials,A as resolveEnvInSecret,M as resolveEnvInSecrets,j as resolveEnvString,P as writeAllCredentials,R as writeSDKCredentials};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "learn-secrets-sdk",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Secure API proxy SDK for static sites
|
|
3
|
+
"version": "1.8.0",
|
|
4
|
+
"description": "Secure API proxy SDK with Cloudflare Worker deployment for static sites",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|