learn-secrets-sdk 1.6.9 → 1.9.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 +386 -274
- 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(
|
|
@@ -539,7 +539,7 @@ function parseEnvFile(filePath) {
|
|
|
539
539
|
if (!trimmed || trimmed.startsWith("#")) {
|
|
540
540
|
continue;
|
|
541
541
|
}
|
|
542
|
-
const match = trimmed.match(/^([A-Z0-9_]+)\s*=\s*(['"]?)(
|
|
542
|
+
const match = trimmed.match(/^([A-Z0-9_]+)\s*=\s*(['"]?)(.+)\2\s*$/i);
|
|
543
543
|
if (match) {
|
|
544
544
|
const key = match[1];
|
|
545
545
|
const value = match[3];
|
|
@@ -585,45 +585,43 @@ var PROVIDER_PATTERNS = [
|
|
|
585
585
|
baseUrl: "https://www.googleapis.com",
|
|
586
586
|
authHeader: "Authorization",
|
|
587
587
|
authPrefix: "Bearer "
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
pattern: /^AUTH0_.*_SECRET$/i,
|
|
591
|
+
provider: "auth0",
|
|
592
|
+
baseUrl: "",
|
|
593
|
+
authHeader: "Authorization",
|
|
594
|
+
authPrefix: "Bearer "
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
pattern: /^SPOTIFY_(CLIENT_ID|CLIENT_SECRET|API_KEY)$/i,
|
|
598
|
+
provider: "spotify",
|
|
599
|
+
baseUrl: "https://api.spotify.com",
|
|
600
|
+
authHeader: "Authorization",
|
|
601
|
+
authPrefix: "Bearer "
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
pattern: /^APPLE_(KEY_ID|TEAM_ID|P8_KEY)$/i,
|
|
605
|
+
provider: "apple",
|
|
606
|
+
baseUrl: "https://api.music.apple.com",
|
|
607
|
+
authHeader: "Authorization",
|
|
608
|
+
authPrefix: "Bearer "
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
pattern: /^LICHESS_KEY$/i,
|
|
612
|
+
provider: "lichess",
|
|
613
|
+
baseUrl: "https://lichess.org/api",
|
|
614
|
+
authHeader: "Authorization",
|
|
615
|
+
authPrefix: "Bearer "
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
pattern: /^GITHUB_PAT$/i,
|
|
619
|
+
provider: "github",
|
|
620
|
+
baseUrl: "https://api.github.com",
|
|
621
|
+
authHeader: "Authorization",
|
|
622
|
+
authPrefix: "Bearer "
|
|
588
623
|
}
|
|
589
624
|
];
|
|
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
625
|
function detectProvider(key) {
|
|
628
626
|
for (const pattern of PROVIDER_PATTERNS) {
|
|
629
627
|
if (pattern.pattern.test(key)) {
|
|
@@ -633,12 +631,12 @@ function detectProvider(key) {
|
|
|
633
631
|
return null;
|
|
634
632
|
}
|
|
635
633
|
function envKeyToSecretName(key) {
|
|
636
|
-
return key.toLowerCase().replace(/
|
|
634
|
+
return key.toLowerCase().replace(/_/g, "-");
|
|
637
635
|
}
|
|
638
|
-
function
|
|
636
|
+
function convertAllEnvVarsToSecrets(envVars) {
|
|
639
637
|
const secrets = [];
|
|
640
638
|
for (const { key, value } of envVars) {
|
|
641
|
-
if (!
|
|
639
|
+
if (!value || value.trim() === "") {
|
|
642
640
|
continue;
|
|
643
641
|
}
|
|
644
642
|
const providerInfo = detectProvider(key);
|
|
@@ -658,7 +656,6 @@ function detectApiKeys(envVars) {
|
|
|
658
656
|
provider: "custom",
|
|
659
657
|
api_key: value,
|
|
660
658
|
base_url: "",
|
|
661
|
-
// User must configure
|
|
662
659
|
auth_header: "Authorization",
|
|
663
660
|
auth_prefix: "Bearer "
|
|
664
661
|
});
|
|
@@ -668,16 +665,117 @@ function detectApiKeys(envVars) {
|
|
|
668
665
|
}
|
|
669
666
|
function summarizeDetectedSecrets(secrets) {
|
|
670
667
|
if (secrets.length === 0) {
|
|
671
|
-
return "No
|
|
668
|
+
return "No secrets found in .env file";
|
|
672
669
|
}
|
|
673
|
-
const lines = ["Detected
|
|
670
|
+
const lines = ["Detected secrets:"];
|
|
674
671
|
for (const secret of secrets) {
|
|
675
|
-
const masked = secret.api_key.substring(0, 8) + "...";
|
|
672
|
+
const masked = secret.api_key.length > 8 ? secret.api_key.substring(0, 8) + "..." : "***";
|
|
676
673
|
lines.push(` - ${secret.name} (${secret.provider}): ${masked}`);
|
|
677
674
|
}
|
|
678
675
|
return lines.join("\n");
|
|
679
676
|
}
|
|
680
677
|
|
|
678
|
+
// src/cli/commands/connect-cloudflare.ts
|
|
679
|
+
function getApiUrl(baseUrl) {
|
|
680
|
+
return baseUrl || "https://cloudprototype.org";
|
|
681
|
+
}
|
|
682
|
+
async function connectCloudflare(apiToken, baseUrl) {
|
|
683
|
+
if (!apiToken.match(/^[A-Za-z0-9_-]{40,}$/)) {
|
|
684
|
+
return {
|
|
685
|
+
success: false,
|
|
686
|
+
error: "Invalid API token format. Token should be 40+ alphanumeric characters"
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
const validation = await validateCloudflareToken(apiToken);
|
|
690
|
+
if (!validation.valid) {
|
|
691
|
+
return {
|
|
692
|
+
success: false,
|
|
693
|
+
error: validation.error || "Token validation failed"
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
try {
|
|
697
|
+
const apiUrl = getApiUrl(baseUrl);
|
|
698
|
+
const response = await fetch(`${apiUrl}/api/cloudflare/connect`, {
|
|
699
|
+
method: "POST",
|
|
700
|
+
headers: {
|
|
701
|
+
"Content-Type": "application/json"
|
|
702
|
+
},
|
|
703
|
+
credentials: "include",
|
|
704
|
+
body: JSON.stringify({
|
|
705
|
+
apiToken,
|
|
706
|
+
accountId: validation.accountId,
|
|
707
|
+
accountName: validation.accountName
|
|
708
|
+
})
|
|
709
|
+
});
|
|
710
|
+
if (!response.ok) {
|
|
711
|
+
const error = await response.json();
|
|
712
|
+
return {
|
|
713
|
+
success: false,
|
|
714
|
+
error: error.message || "Failed to store token"
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
return {
|
|
718
|
+
success: true,
|
|
719
|
+
accountId: validation.accountId,
|
|
720
|
+
accountName: validation.accountName
|
|
721
|
+
};
|
|
722
|
+
} catch (error) {
|
|
723
|
+
return {
|
|
724
|
+
success: false,
|
|
725
|
+
error: error.message || "Failed to connect Cloudflare account"
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
async function validateCloudflareToken(token) {
|
|
730
|
+
try {
|
|
731
|
+
const response = await fetch("https://api.cloudflare.com/client/v4/user", {
|
|
732
|
+
headers: {
|
|
733
|
+
Authorization: `Bearer ${token}`,
|
|
734
|
+
"Content-Type": "application/json"
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
if (!response.ok) {
|
|
738
|
+
const error = await response.json();
|
|
739
|
+
return {
|
|
740
|
+
valid: false,
|
|
741
|
+
error: error.errors?.[0]?.message || "Invalid token"
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
const data = await response.json();
|
|
745
|
+
const accountsResponse = await fetch("https://api.cloudflare.com/client/v4/accounts", {
|
|
746
|
+
headers: {
|
|
747
|
+
Authorization: `Bearer ${token}`,
|
|
748
|
+
"Content-Type": "application/json"
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
if (!accountsResponse.ok) {
|
|
752
|
+
return {
|
|
753
|
+
valid: false,
|
|
754
|
+
error: "Token does not have access to accounts"
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
const accountsData = await accountsResponse.json();
|
|
758
|
+
const accounts = accountsData.result || [];
|
|
759
|
+
if (accounts.length === 0) {
|
|
760
|
+
return {
|
|
761
|
+
valid: false,
|
|
762
|
+
error: "No accounts found for this token"
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
const account = accounts[0];
|
|
766
|
+
return {
|
|
767
|
+
valid: true,
|
|
768
|
+
accountId: account.id,
|
|
769
|
+
accountName: account.name
|
|
770
|
+
};
|
|
771
|
+
} catch (error) {
|
|
772
|
+
return {
|
|
773
|
+
valid: false,
|
|
774
|
+
error: error.message || "Failed to validate token"
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
681
779
|
// src/cli/commands/init.ts
|
|
682
780
|
async function initCommand(options) {
|
|
683
781
|
if (!hasValidCredentials()) {
|
|
@@ -720,12 +818,10 @@ Onboarding site for project: ${projectId}`);
|
|
|
720
818
|
try {
|
|
721
819
|
console.log(`Reading ${envFile}...`);
|
|
722
820
|
const envVars = parseEnvFile(envFile);
|
|
723
|
-
secrets =
|
|
821
|
+
secrets = convertAllEnvVarsToSecrets(envVars);
|
|
724
822
|
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-...");
|
|
823
|
+
console.log("\nNo environment variables found in .env file.");
|
|
824
|
+
console.log("Make sure your .env file is not empty.");
|
|
729
825
|
process.exit(0);
|
|
730
826
|
}
|
|
731
827
|
console.log("\n" + summarizeDetectedSecrets(secrets));
|
|
@@ -773,15 +869,67 @@ Onboarding site for project: ${projectId}`);
|
|
|
773
869
|
console.log("\nAdd to your static site code:");
|
|
774
870
|
console.log(" const sdk = new SecretsSDK();");
|
|
775
871
|
console.log(" // Uses origin-based auth - no token needed!");
|
|
872
|
+
console.log("\n--- Cloudflare Worker Setup ---");
|
|
776
873
|
const dashboardUrl = options.baseUrl || "https://cloudprototype.org";
|
|
874
|
+
let cloudflareConnected = false;
|
|
875
|
+
try {
|
|
876
|
+
const statusResponse = await fetch(`${dashboardUrl}/api/cloudflare/status`, {
|
|
877
|
+
credentials: "include"
|
|
878
|
+
});
|
|
879
|
+
cloudflareConnected = statusResponse.ok;
|
|
880
|
+
} catch {
|
|
881
|
+
cloudflareConnected = false;
|
|
882
|
+
}
|
|
883
|
+
if (cloudflareConnected) {
|
|
884
|
+
console.log("Cloudflare account is already connected.");
|
|
885
|
+
} else {
|
|
886
|
+
console.log("To deploy secrets via Cloudflare Worker, we need your Cloudflare API token.");
|
|
887
|
+
console.log("\nRequired permissions:");
|
|
888
|
+
console.log(" - Workers Scripts: Edit");
|
|
889
|
+
console.log(" - Workers Routes: Edit");
|
|
890
|
+
console.log(" - Account Settings: Read");
|
|
891
|
+
console.log("\nCreate token at: https://dash.cloudflare.com/profile/api-tokens");
|
|
892
|
+
const readline = await import("readline");
|
|
893
|
+
const rl = readline.createInterface({
|
|
894
|
+
input: process.stdin,
|
|
895
|
+
output: process.stdout
|
|
896
|
+
});
|
|
897
|
+
const apiToken = await new Promise((resolve2) => {
|
|
898
|
+
rl.question("\nCloudflare API Token (or press Enter to skip): ", (answer) => {
|
|
899
|
+
rl.close();
|
|
900
|
+
resolve2(answer.trim());
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
if (apiToken) {
|
|
904
|
+
console.log("\nValidating token...");
|
|
905
|
+
const result2 = await connectCloudflare(apiToken, options.baseUrl);
|
|
906
|
+
if (result2.success) {
|
|
907
|
+
console.log("Cloudflare account connected");
|
|
908
|
+
console.log(` Account: ${result2.accountName}`);
|
|
909
|
+
cloudflareConnected = true;
|
|
910
|
+
} else {
|
|
911
|
+
console.log(`
|
|
912
|
+
Warning: Failed to connect Cloudflare: ${result2.error}`);
|
|
913
|
+
console.log("You can connect later by running: learn-secrets deploy");
|
|
914
|
+
}
|
|
915
|
+
} else {
|
|
916
|
+
console.log("\nSkipped Cloudflare connection.");
|
|
917
|
+
console.log("You can connect later when you run: learn-secrets deploy");
|
|
918
|
+
}
|
|
919
|
+
}
|
|
777
920
|
console.log(`
|
|
778
921
|
Next steps:`);
|
|
779
922
|
console.log(` 1. Run: learn-secrets deploy`);
|
|
780
|
-
|
|
923
|
+
if (cloudflareConnected) {
|
|
924
|
+
console.log(` (Deploy Worker to Cloudflare with your secrets)`);
|
|
925
|
+
} else {
|
|
926
|
+
console.log(` (Connect Cloudflare and deploy Worker with your secrets)`);
|
|
927
|
+
}
|
|
781
928
|
console.log(` 2. Visit: ${dashboardUrl}/dashboard to manage secrets`);
|
|
782
929
|
const config = {
|
|
783
930
|
project: projectId,
|
|
784
|
-
origins
|
|
931
|
+
origins,
|
|
932
|
+
env: envFile
|
|
785
933
|
};
|
|
786
934
|
fs3.writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
787
935
|
console.log(`
|
|
@@ -798,212 +946,6 @@ Updated local config: ${configFile}`);
|
|
|
798
946
|
}
|
|
799
947
|
}
|
|
800
948
|
|
|
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
949
|
// src/cli/commands/config.ts
|
|
1008
950
|
import fs4 from "fs";
|
|
1009
951
|
async function configCommand(options) {
|
|
@@ -1014,15 +956,17 @@ async function configCommand(options) {
|
|
|
1014
956
|
process.exit(1);
|
|
1015
957
|
}
|
|
1016
958
|
const config = JSON.parse(fs4.readFileSync(configFile, "utf8"));
|
|
1017
|
-
if (!options.project && !options.origins) {
|
|
959
|
+
if (!options.project && !options.origins && !options.env) {
|
|
1018
960
|
console.log("\nCurrent configuration:");
|
|
1019
961
|
console.log(` Project ID: ${config.project || "(not set)"}`);
|
|
1020
962
|
console.log(` Origins: ${config.origins?.length > 0 ? config.origins.join(", ") : "(not set)"}`);
|
|
963
|
+
console.log(` Env file: ${config.env || ".env (default)"}`);
|
|
1021
964
|
console.log(`
|
|
1022
965
|
Config file: ${configFile}`);
|
|
1023
966
|
console.log("\nTo update:");
|
|
1024
967
|
console.log(" learn-secrets config --project <appid>");
|
|
1025
968
|
console.log(" learn-secrets config --origins domain1.com,domain2.com");
|
|
969
|
+
console.log(" learn-secrets config --env .custom_env");
|
|
1026
970
|
return;
|
|
1027
971
|
}
|
|
1028
972
|
let updated = false;
|
|
@@ -1036,6 +980,11 @@ Config file: ${configFile}`);
|
|
|
1036
980
|
console.log(`Updated origins: ${config.origins.join(", ")}`);
|
|
1037
981
|
updated = true;
|
|
1038
982
|
}
|
|
983
|
+
if (options.env) {
|
|
984
|
+
config.env = options.env;
|
|
985
|
+
console.log(`Updated env file path: ${options.env}`);
|
|
986
|
+
updated = true;
|
|
987
|
+
}
|
|
1039
988
|
if (updated) {
|
|
1040
989
|
fs4.writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
1041
990
|
console.log(`
|
|
@@ -1045,6 +994,87 @@ Configuration saved to: ${configFile}`);
|
|
|
1045
994
|
|
|
1046
995
|
// src/cli/commands/deploy.ts
|
|
1047
996
|
import fs5 from "fs";
|
|
997
|
+
|
|
998
|
+
// src/cli/commands/provision-worker.ts
|
|
999
|
+
import path2 from "path";
|
|
1000
|
+
import { fileURLToPath } from "url";
|
|
1001
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
1002
|
+
var __dirname = path2.dirname(__filename);
|
|
1003
|
+
function getApiUrl2(baseUrl) {
|
|
1004
|
+
return baseUrl || "https://cloudprototype.org";
|
|
1005
|
+
}
|
|
1006
|
+
async function provisionWorker(workerName, origins, envVars, baseUrl) {
|
|
1007
|
+
const publicVars = [];
|
|
1008
|
+
const secretVars = [];
|
|
1009
|
+
for (const { key, value } of envVars) {
|
|
1010
|
+
if (isPublicConfig(key)) {
|
|
1011
|
+
publicVars.push({ key, value });
|
|
1012
|
+
} else {
|
|
1013
|
+
secretVars.push({ key, value });
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
try {
|
|
1017
|
+
const apiUrl = getApiUrl2(baseUrl);
|
|
1018
|
+
const deployResponse = await fetch(`${apiUrl}/api/cloudflare/provision-worker`, {
|
|
1019
|
+
method: "POST",
|
|
1020
|
+
headers: {
|
|
1021
|
+
"Content-Type": "application/json"
|
|
1022
|
+
},
|
|
1023
|
+
credentials: "include",
|
|
1024
|
+
body: JSON.stringify({
|
|
1025
|
+
workerName,
|
|
1026
|
+
allowedOrigins: origins,
|
|
1027
|
+
publicVars: publicVars.map((v) => ({
|
|
1028
|
+
key: `PUBLIC_${v.key}`,
|
|
1029
|
+
value: v.value
|
|
1030
|
+
})),
|
|
1031
|
+
secrets: secretVars.map((v) => ({
|
|
1032
|
+
key: `SECRET_${v.key}`,
|
|
1033
|
+
value: v.value
|
|
1034
|
+
}))
|
|
1035
|
+
})
|
|
1036
|
+
});
|
|
1037
|
+
if (!deployResponse.ok) {
|
|
1038
|
+
const error = await deployResponse.json();
|
|
1039
|
+
return {
|
|
1040
|
+
success: false,
|
|
1041
|
+
error: error.message || "Failed to deploy Worker"
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
const result = await deployResponse.json();
|
|
1045
|
+
return {
|
|
1046
|
+
success: true,
|
|
1047
|
+
workerUrl: result.workerUrl,
|
|
1048
|
+
workerName: result.workerName
|
|
1049
|
+
};
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
return {
|
|
1052
|
+
success: false,
|
|
1053
|
+
error: error.message || "Failed to provision Worker"
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function isPublicConfig(key) {
|
|
1058
|
+
const publicPatterns = [
|
|
1059
|
+
/_DOMAIN$/,
|
|
1060
|
+
/_CLIENT_ID$/,
|
|
1061
|
+
/_AUDIENCE$/,
|
|
1062
|
+
/_CALLBACK_URL$/,
|
|
1063
|
+
/_ISSUER$/,
|
|
1064
|
+
/_TEAM_ID$/,
|
|
1065
|
+
/_KEY_ID$/,
|
|
1066
|
+
/^API_BASE_URL$/,
|
|
1067
|
+
/^PUBLIC_/
|
|
1068
|
+
];
|
|
1069
|
+
for (const pattern of publicPatterns) {
|
|
1070
|
+
if (pattern.test(key)) {
|
|
1071
|
+
return true;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// src/cli/commands/deploy.ts
|
|
1048
1078
|
async function deployCommand(options = {}) {
|
|
1049
1079
|
if (!hasValidCredentials()) {
|
|
1050
1080
|
console.error('Error: Not authenticated. Run "learn-secrets login" first.');
|
|
@@ -1071,17 +1101,16 @@ async function deployCommand(options = {}) {
|
|
|
1071
1101
|
Deploying secrets to project: ${config.project}`);
|
|
1072
1102
|
console.log(`Origins: ${config.origins.join(", ")}
|
|
1073
1103
|
`);
|
|
1074
|
-
const envFile = options.env || ".env";
|
|
1104
|
+
const envFile = options.env || config.env || ".env";
|
|
1075
1105
|
let secrets;
|
|
1106
|
+
let envVars = [];
|
|
1076
1107
|
try {
|
|
1077
1108
|
console.log(`Reading ${envFile}...`);
|
|
1078
|
-
|
|
1079
|
-
secrets =
|
|
1109
|
+
envVars = parseEnvFile(envFile);
|
|
1110
|
+
secrets = convertAllEnvVarsToSecrets(envVars);
|
|
1080
1111
|
if (secrets.length === 0) {
|
|
1081
|
-
console.log("\nNo
|
|
1082
|
-
console.log("Make sure your .env
|
|
1083
|
-
console.log(" OPENAI_API_KEY=sk-...");
|
|
1084
|
-
console.log(" ANTHROPIC_API_KEY=sk-ant-...");
|
|
1112
|
+
console.log("\nNo environment variables found in .env file.");
|
|
1113
|
+
console.log("Make sure your .env file is not empty.");
|
|
1085
1114
|
process.exit(0);
|
|
1086
1115
|
}
|
|
1087
1116
|
console.log("\n" + summarizeDetectedSecrets(secrets));
|
|
@@ -1127,7 +1156,93 @@ Deploying secrets to project: ${config.project}`);
|
|
|
1127
1156
|
}
|
|
1128
1157
|
const dashboardUrl = options.baseUrl || "https://cloudprototype.org";
|
|
1129
1158
|
console.log(`
|
|
1130
|
-
Secrets deployed! View them at: ${dashboardUrl}/dashboard/secrets
|
|
1159
|
+
Secrets deployed! View them at: ${dashboardUrl}/dashboard/collections/secrets`);
|
|
1160
|
+
console.log("\n--- Cloudflare Worker Deployment ---");
|
|
1161
|
+
let cloudflareConnected = false;
|
|
1162
|
+
try {
|
|
1163
|
+
const statusResponse = await fetch(`${dashboardUrl}/api/cloudflare/status`, {
|
|
1164
|
+
credentials: "include"
|
|
1165
|
+
});
|
|
1166
|
+
cloudflareConnected = statusResponse.ok;
|
|
1167
|
+
} catch {
|
|
1168
|
+
cloudflareConnected = false;
|
|
1169
|
+
}
|
|
1170
|
+
if (!cloudflareConnected) {
|
|
1171
|
+
console.log("Cloudflare account not connected.");
|
|
1172
|
+
console.log("\nTo deploy Worker, we need your Cloudflare API token.");
|
|
1173
|
+
console.log("Required permissions:");
|
|
1174
|
+
console.log(" - Workers Scripts: Edit");
|
|
1175
|
+
console.log(" - Workers Routes: Edit");
|
|
1176
|
+
console.log(" - Account Settings: Read");
|
|
1177
|
+
console.log("\nCreate token at: https://dash.cloudflare.com/profile/api-tokens");
|
|
1178
|
+
const readline = await import("readline");
|
|
1179
|
+
const rl = readline.createInterface({
|
|
1180
|
+
input: process.stdin,
|
|
1181
|
+
output: process.stdout
|
|
1182
|
+
});
|
|
1183
|
+
const apiToken = await new Promise((resolve2) => {
|
|
1184
|
+
rl.question("\nCloudflare API Token (or press Enter to skip): ", (answer) => {
|
|
1185
|
+
rl.close();
|
|
1186
|
+
resolve2(answer.trim());
|
|
1187
|
+
});
|
|
1188
|
+
});
|
|
1189
|
+
if (apiToken) {
|
|
1190
|
+
console.log("\nValidating token...");
|
|
1191
|
+
const result2 = await connectCloudflare(apiToken, options.baseUrl);
|
|
1192
|
+
if (result2.success) {
|
|
1193
|
+
console.log("Cloudflare account connected");
|
|
1194
|
+
console.log(` Account: ${result2.accountName}`);
|
|
1195
|
+
cloudflareConnected = true;
|
|
1196
|
+
} else {
|
|
1197
|
+
console.log(`
|
|
1198
|
+
Error: Failed to connect Cloudflare: ${result2.error}`);
|
|
1199
|
+
console.log("Worker deployment skipped.");
|
|
1200
|
+
}
|
|
1201
|
+
} else {
|
|
1202
|
+
console.log("\nSkipped Cloudflare connection. Worker deployment skipped.");
|
|
1203
|
+
}
|
|
1204
|
+
} else {
|
|
1205
|
+
console.log("Cloudflare account is connected.");
|
|
1206
|
+
}
|
|
1207
|
+
if (cloudflareConnected) {
|
|
1208
|
+
const readline = await import("readline");
|
|
1209
|
+
const rl = readline.createInterface({
|
|
1210
|
+
input: process.stdin,
|
|
1211
|
+
output: process.stdout
|
|
1212
|
+
});
|
|
1213
|
+
const shouldProvision = await new Promise((resolve2) => {
|
|
1214
|
+
rl.question("\nDeploy/update Worker to Cloudflare? (y/N): ", (answer) => {
|
|
1215
|
+
rl.close();
|
|
1216
|
+
resolve2(answer.trim());
|
|
1217
|
+
});
|
|
1218
|
+
});
|
|
1219
|
+
if (shouldProvision.toLowerCase() === "y" || shouldProvision.toLowerCase() === "yes") {
|
|
1220
|
+
console.log("\nDeploying Worker to Cloudflare...");
|
|
1221
|
+
const workerName = `learn-secrets-${config.project.toLowerCase()}`;
|
|
1222
|
+
const provisionResult = await provisionWorker(
|
|
1223
|
+
workerName,
|
|
1224
|
+
config.origins,
|
|
1225
|
+
envVars,
|
|
1226
|
+
options.baseUrl
|
|
1227
|
+
);
|
|
1228
|
+
if (provisionResult.success) {
|
|
1229
|
+
console.log("\nWorker deployed successfully");
|
|
1230
|
+
console.log(` Name: ${provisionResult.workerName}`);
|
|
1231
|
+
console.log(` URL: ${provisionResult.workerUrl}`);
|
|
1232
|
+
config.workerUrl = provisionResult.workerUrl;
|
|
1233
|
+
fs5.writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
1234
|
+
console.log("\nNext steps:");
|
|
1235
|
+
console.log(` 1. Test Worker: curl ${provisionResult.workerUrl}/health`);
|
|
1236
|
+
console.log(` 2. Update your frontend to use Worker URL`);
|
|
1237
|
+
console.log(` 3. Test origin validation from your site`);
|
|
1238
|
+
} else {
|
|
1239
|
+
console.log(`
|
|
1240
|
+
Error: Worker deployment failed: ${provisionResult.error}`);
|
|
1241
|
+
}
|
|
1242
|
+
} else {
|
|
1243
|
+
console.log("\nSkipped Worker deployment.");
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1131
1246
|
} catch (error) {
|
|
1132
1247
|
console.error("\nDeploy failed:", error.message);
|
|
1133
1248
|
if (error.status === 401) {
|
|
@@ -1142,17 +1257,14 @@ Secrets deployed! View them at: ${dashboardUrl}/dashboard/secrets/api_keys`);
|
|
|
1142
1257
|
|
|
1143
1258
|
// src/cli/index.ts
|
|
1144
1259
|
var program = new Command();
|
|
1145
|
-
program.name("learn-secrets").description("CLI tool for managing API secrets
|
|
1146
|
-
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) => {
|
|
1147
|
-
await setupCommand(options);
|
|
1148
|
-
});
|
|
1260
|
+
program.name("learn-secrets").description("CLI tool for managing API secrets with Cloudflare Worker deployment").version("1.9.0");
|
|
1149
1261
|
program.command("login").description("Authenticate with Learn Secrets via browser").option("--base-url <url>", "Custom base URL for API").action(async (options) => {
|
|
1150
1262
|
await loginCommand(options);
|
|
1151
1263
|
});
|
|
1152
1264
|
program.command("init").description("Initialize secrets for a new site").requiredOption("-o, --origins <domains>", "Comma-separated list of allowed origins (e.g., mysite.com,localhost)").option("-p, --project <appid>", "Project app ID (auto-detected from login if not specified)").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) => {
|
|
1153
1265
|
await initCommand(options);
|
|
1154
1266
|
});
|
|
1155
|
-
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) => {
|
|
1267
|
+
program.command("config").description("View or update project configuration").option("-p, --project <appid>", "Change project ID").option("--origins <origins>", "Comma separated origins").option("-e, --env <file>", "Path to .env file").action(async (options) => {
|
|
1156
1268
|
await configCommand(options);
|
|
1157
1269
|
});
|
|
1158
1270
|
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) => {
|
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.9.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",
|