@lanonasis/oauth-client 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -5
- package/dist/browser-CUJNgghM.d.cts +246 -0
- package/dist/browser-CUJNgghM.d.ts +246 -0
- package/dist/browser.cjs +1312 -0
- package/dist/browser.d.cts +1 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.mjs +1286 -0
- package/dist/index.cjs +386 -79
- package/dist/index.d.cts +3 -196
- package/dist/index.d.ts +3 -196
- package/dist/{index.js → index.mjs} +383 -67
- package/package.json +15 -8
|
@@ -5,12 +5,16 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
5
5
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
+
// src/flows/terminal-flow.ts
|
|
9
|
+
import fetch2 from "cross-fetch";
|
|
10
|
+
|
|
8
11
|
// src/flows/base-flow.ts
|
|
12
|
+
import fetch from "cross-fetch";
|
|
9
13
|
var BaseOAuthFlow = class {
|
|
10
14
|
constructor(config) {
|
|
11
15
|
this.clientId = config.clientId;
|
|
12
16
|
this.authBaseUrl = config.authBaseUrl || "https://auth.lanonasis.com";
|
|
13
|
-
this.scope = config.scope || "
|
|
17
|
+
this.scope = config.scope || "memories:read memories:write memories:delete profile";
|
|
14
18
|
}
|
|
15
19
|
async makeTokenRequest(body) {
|
|
16
20
|
const response = await fetch(`${this.authBaseUrl}/oauth/token`, {
|
|
@@ -26,11 +30,10 @@ var BaseOAuthFlow = class {
|
|
|
26
30
|
}
|
|
27
31
|
generateState() {
|
|
28
32
|
const array = new Uint8Array(32);
|
|
29
|
-
if (typeof
|
|
30
|
-
|
|
33
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
34
|
+
crypto.getRandomValues(array);
|
|
31
35
|
} else {
|
|
32
|
-
|
|
33
|
-
crypto.randomFillSync(array);
|
|
36
|
+
throw new Error("Secure random generation is not available");
|
|
34
37
|
}
|
|
35
38
|
return this.base64URLEncode(array);
|
|
36
39
|
}
|
|
@@ -40,7 +43,8 @@ var BaseOAuthFlow = class {
|
|
|
40
43
|
bytes.forEach((byte) => {
|
|
41
44
|
binary += String.fromCharCode(byte);
|
|
42
45
|
});
|
|
43
|
-
|
|
46
|
+
const base64 = typeof btoa !== "undefined" ? btoa(binary) : Buffer.from(bytes).toString("base64");
|
|
47
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
44
48
|
}
|
|
45
49
|
async refreshToken(refreshToken) {
|
|
46
50
|
return this.makeTokenRequest({
|
|
@@ -66,7 +70,6 @@ var BaseOAuthFlow = class {
|
|
|
66
70
|
};
|
|
67
71
|
|
|
68
72
|
// src/flows/terminal-flow.ts
|
|
69
|
-
import open from "open";
|
|
70
73
|
var TerminalOAuthFlow = class extends BaseOAuthFlow {
|
|
71
74
|
constructor(config) {
|
|
72
75
|
super({
|
|
@@ -89,7 +92,7 @@ var TerminalOAuthFlow = class extends BaseOAuthFlow {
|
|
|
89
92
|
}
|
|
90
93
|
}
|
|
91
94
|
async requestDeviceCode() {
|
|
92
|
-
const response = await
|
|
95
|
+
const response = await fetch2(`${this.authBaseUrl}/oauth/device`, {
|
|
93
96
|
method: "POST",
|
|
94
97
|
headers: { "Content-Type": "application/json" },
|
|
95
98
|
body: JSON.stringify({
|
|
@@ -114,6 +117,7 @@ var TerminalOAuthFlow = class extends BaseOAuthFlow {
|
|
|
114
117
|
}
|
|
115
118
|
async openBrowser(url) {
|
|
116
119
|
try {
|
|
120
|
+
const { default: open } = await import("open");
|
|
117
121
|
await Promise.race([
|
|
118
122
|
this.waitForEnter(),
|
|
119
123
|
new Promise((resolve) => setTimeout(resolve, 2e3))
|
|
@@ -162,7 +166,7 @@ var TerminalOAuthFlow = class extends BaseOAuthFlow {
|
|
|
162
166
|
throw new Error("Authorization timeout - please try again");
|
|
163
167
|
}
|
|
164
168
|
async checkDeviceCode(deviceCode) {
|
|
165
|
-
const response = await
|
|
169
|
+
const response = await fetch2(`${this.authBaseUrl}/oauth/token`, {
|
|
166
170
|
method: "POST",
|
|
167
171
|
headers: { "Content-Type": "application/json" },
|
|
168
172
|
body: JSON.stringify({
|
|
@@ -203,25 +207,22 @@ var DesktopOAuthFlow = class extends BaseOAuthFlow {
|
|
|
203
207
|
}
|
|
204
208
|
generateCodeVerifier() {
|
|
205
209
|
const array = new Uint8Array(32);
|
|
206
|
-
if (typeof
|
|
207
|
-
|
|
210
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
211
|
+
crypto.getRandomValues(array);
|
|
208
212
|
} else {
|
|
209
|
-
|
|
210
|
-
crypto.randomFillSync(array);
|
|
213
|
+
throw new Error("Secure random generation is not available in this environment");
|
|
211
214
|
}
|
|
212
215
|
return this.base64URLEncode(array);
|
|
213
216
|
}
|
|
214
217
|
async generateCodeChallenge(verifier) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const hash = await window.crypto.subtle.digest("SHA-256", data);
|
|
219
|
-
return this.base64URLEncode(hash);
|
|
220
|
-
} else {
|
|
221
|
-
const crypto = __require("crypto");
|
|
222
|
-
const hash = crypto.createHash("sha256").update(verifier).digest();
|
|
223
|
-
return this.base64URLEncode(hash);
|
|
218
|
+
const subtle = typeof crypto !== "undefined" ? crypto.subtle : void 0;
|
|
219
|
+
if (!subtle) {
|
|
220
|
+
throw new Error("Web Crypto is required to generate PKCE code challenge");
|
|
224
221
|
}
|
|
222
|
+
const encoder = new TextEncoder();
|
|
223
|
+
const data = encoder.encode(verifier);
|
|
224
|
+
const hash = await subtle.digest("SHA-256", data);
|
|
225
|
+
return this.base64URLEncode(hash);
|
|
225
226
|
}
|
|
226
227
|
buildAuthorizationUrl(codeChallenge, state) {
|
|
227
228
|
const params = new URLSearchParams({
|
|
@@ -423,13 +424,13 @@ var TokenStorage = class {
|
|
|
423
424
|
const fs = __require("fs").promises;
|
|
424
425
|
const path = __require("path");
|
|
425
426
|
const os = __require("os");
|
|
426
|
-
const
|
|
427
|
+
const crypto2 = __require("crypto");
|
|
427
428
|
const configDir = path.join(os.homedir(), ".lanonasis");
|
|
428
429
|
const tokenFile = path.join(configDir, "mcp-tokens.enc");
|
|
429
430
|
await fs.mkdir(configDir, { recursive: true });
|
|
430
431
|
const key = this.getFileEncryptionKey();
|
|
431
|
-
const iv =
|
|
432
|
-
const cipher =
|
|
432
|
+
const iv = crypto2.randomBytes(16);
|
|
433
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
|
|
433
434
|
let encrypted = cipher.update(tokenString, "utf8", "hex");
|
|
434
435
|
encrypted += cipher.final("hex");
|
|
435
436
|
const authTag = cipher.getAuthTag().toString("hex");
|
|
@@ -441,7 +442,7 @@ var TokenStorage = class {
|
|
|
441
442
|
const fs = __require("fs").promises;
|
|
442
443
|
const path = __require("path");
|
|
443
444
|
const os = __require("os");
|
|
444
|
-
const
|
|
445
|
+
const crypto2 = __require("crypto");
|
|
445
446
|
const tokenFile = path.join(os.homedir(), ".lanonasis", "mcp-tokens.enc");
|
|
446
447
|
try {
|
|
447
448
|
const data = await fs.readFile(tokenFile, "utf8");
|
|
@@ -451,7 +452,7 @@ var TokenStorage = class {
|
|
|
451
452
|
const [ivHex, authTagHex, encrypted] = parts;
|
|
452
453
|
const iv = Buffer.from(ivHex, "hex");
|
|
453
454
|
const authTag = Buffer.from(authTagHex, "hex");
|
|
454
|
-
const decipher =
|
|
455
|
+
const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
|
|
455
456
|
decipher.setAuthTag(authTag);
|
|
456
457
|
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
457
458
|
decrypted += decipher.final("utf8");
|
|
@@ -460,7 +461,7 @@ var TokenStorage = class {
|
|
|
460
461
|
if (parts.length === 2) {
|
|
461
462
|
const [ivHex, encrypted] = parts;
|
|
462
463
|
const iv = Buffer.from(ivHex, "hex");
|
|
463
|
-
const decipher =
|
|
464
|
+
const decipher = crypto2.createDecipheriv("aes-256-cbc", key, iv);
|
|
464
465
|
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
465
466
|
decrypted += decipher.final("utf8");
|
|
466
467
|
return decrypted;
|
|
@@ -482,17 +483,17 @@ var TokenStorage = class {
|
|
|
482
483
|
}
|
|
483
484
|
}
|
|
484
485
|
getFileEncryptionKey() {
|
|
485
|
-
const
|
|
486
|
+
const crypto2 = __require("crypto");
|
|
486
487
|
const os = __require("os");
|
|
487
488
|
const machineId = os.hostname() + os.userInfo().username;
|
|
488
489
|
const salt = "lanonasis-mcp-oauth-2024";
|
|
489
|
-
return
|
|
490
|
+
return crypto2.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
|
|
490
491
|
}
|
|
491
492
|
async encrypt(text) {
|
|
492
493
|
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
493
494
|
const encoder2 = new TextEncoder();
|
|
494
495
|
const data2 = encoder2.encode(text);
|
|
495
|
-
return
|
|
496
|
+
return this.base64Encode(data2);
|
|
496
497
|
}
|
|
497
498
|
const encoder = new TextEncoder();
|
|
498
499
|
const data = encoder.encode(text);
|
|
@@ -525,23 +526,15 @@ var TokenStorage = class {
|
|
|
525
526
|
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
526
527
|
combined.set(iv, 0);
|
|
527
528
|
combined.set(new Uint8Array(encrypted), iv.length);
|
|
528
|
-
return
|
|
529
|
+
return this.base64Encode(combined);
|
|
529
530
|
}
|
|
530
531
|
async decrypt(encrypted) {
|
|
531
532
|
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
532
|
-
const
|
|
533
|
-
const bytes2 = new Uint8Array(binary2.length);
|
|
534
|
-
for (let i = 0; i < binary2.length; i++) {
|
|
535
|
-
bytes2[i] = binary2.charCodeAt(i);
|
|
536
|
-
}
|
|
533
|
+
const bytes2 = this.base64Decode(encrypted);
|
|
537
534
|
const decoder2 = new TextDecoder();
|
|
538
535
|
return decoder2.decode(bytes2);
|
|
539
536
|
}
|
|
540
|
-
const
|
|
541
|
-
const bytes = new Uint8Array(binary.length);
|
|
542
|
-
for (let i = 0; i < binary.length; i++) {
|
|
543
|
-
bytes[i] = binary.charCodeAt(i);
|
|
544
|
-
}
|
|
537
|
+
const bytes = this.base64Decode(encrypted);
|
|
545
538
|
const iv = bytes.slice(0, 12);
|
|
546
539
|
const data = bytes.slice(12);
|
|
547
540
|
const encoder = new TextEncoder();
|
|
@@ -582,6 +575,33 @@ var TokenStorage = class {
|
|
|
582
575
|
isMobile() {
|
|
583
576
|
return typeof window !== "undefined" && window.SecureStorage !== void 0;
|
|
584
577
|
}
|
|
578
|
+
base64Encode(bytes) {
|
|
579
|
+
if (typeof btoa !== "undefined") {
|
|
580
|
+
let binary = "";
|
|
581
|
+
bytes.forEach((b) => {
|
|
582
|
+
binary += String.fromCharCode(b);
|
|
583
|
+
});
|
|
584
|
+
return btoa(binary);
|
|
585
|
+
}
|
|
586
|
+
if (typeof Buffer !== "undefined") {
|
|
587
|
+
return Buffer.from(bytes).toString("base64");
|
|
588
|
+
}
|
|
589
|
+
throw new Error("No base64 encoder available");
|
|
590
|
+
}
|
|
591
|
+
base64Decode(value) {
|
|
592
|
+
if (typeof atob !== "undefined") {
|
|
593
|
+
const binary = atob(value);
|
|
594
|
+
const bytes = new Uint8Array(binary.length);
|
|
595
|
+
for (let i = 0; i < binary.length; i++) {
|
|
596
|
+
bytes[i] = binary.charCodeAt(i);
|
|
597
|
+
}
|
|
598
|
+
return bytes;
|
|
599
|
+
}
|
|
600
|
+
if (typeof Buffer !== "undefined") {
|
|
601
|
+
return new Uint8Array(Buffer.from(value, "base64"));
|
|
602
|
+
}
|
|
603
|
+
throw new Error("No base64 decoder available");
|
|
604
|
+
}
|
|
585
605
|
async getWebEncryptionKey() {
|
|
586
606
|
const existing = typeof localStorage !== "undefined" ? localStorage.getItem(this.webEncryptionKeyStorage) : null;
|
|
587
607
|
if (existing) {
|
|
@@ -593,7 +613,8 @@ var TokenStorage = class {
|
|
|
593
613
|
window.crypto.getRandomValues(buf);
|
|
594
614
|
raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
595
615
|
} else {
|
|
596
|
-
|
|
616
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "node";
|
|
617
|
+
raw = `${ua}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
|
|
597
618
|
}
|
|
598
619
|
if (typeof localStorage !== "undefined") {
|
|
599
620
|
localStorage.setItem(this.webEncryptionKeyStorage, raw);
|
|
@@ -805,13 +826,13 @@ var ApiKeyStorage = class {
|
|
|
805
826
|
const fs = __require("fs").promises;
|
|
806
827
|
const path = __require("path");
|
|
807
828
|
const os = __require("os");
|
|
808
|
-
const
|
|
829
|
+
const crypto2 = __require("crypto");
|
|
809
830
|
const configDir = path.join(os.homedir(), ".lanonasis");
|
|
810
831
|
const keyFile = path.join(configDir, "api-key.enc");
|
|
811
832
|
await fs.mkdir(configDir, { recursive: true, mode: 448 });
|
|
812
833
|
const key = this.getFileEncryptionKey();
|
|
813
|
-
const iv =
|
|
814
|
-
const cipher =
|
|
834
|
+
const iv = crypto2.randomBytes(16);
|
|
835
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
|
|
815
836
|
let encrypted = cipher.update(keyString, "utf8", "hex");
|
|
816
837
|
encrypted += cipher.final("hex");
|
|
817
838
|
const authTag = cipher.getAuthTag();
|
|
@@ -823,7 +844,7 @@ var ApiKeyStorage = class {
|
|
|
823
844
|
const fs = __require("fs").promises;
|
|
824
845
|
const path = __require("path");
|
|
825
846
|
const os = __require("os");
|
|
826
|
-
const
|
|
847
|
+
const crypto2 = __require("crypto");
|
|
827
848
|
const keyFile = path.join(os.homedir(), ".lanonasis", "api-key.enc");
|
|
828
849
|
try {
|
|
829
850
|
const data = await fs.readFile(keyFile, "utf8");
|
|
@@ -834,7 +855,7 @@ var ApiKeyStorage = class {
|
|
|
834
855
|
const key = this.getFileEncryptionKey();
|
|
835
856
|
const iv = Buffer.from(ivHex, "hex");
|
|
836
857
|
const authTag = Buffer.from(authTagHex, "hex");
|
|
837
|
-
const decipher =
|
|
858
|
+
const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
|
|
838
859
|
decipher.setAuthTag(authTag);
|
|
839
860
|
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
840
861
|
decrypted += decipher.final("utf8");
|
|
@@ -878,18 +899,18 @@ var ApiKeyStorage = class {
|
|
|
878
899
|
}
|
|
879
900
|
}
|
|
880
901
|
getFileEncryptionKey() {
|
|
881
|
-
const
|
|
902
|
+
const crypto2 = __require("crypto");
|
|
882
903
|
const os = __require("os");
|
|
883
904
|
const machineId = os.hostname() + os.userInfo().username;
|
|
884
905
|
const salt = "lanonasis-mcp-api-key-2024";
|
|
885
|
-
return
|
|
906
|
+
return crypto2.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
|
|
886
907
|
}
|
|
887
908
|
// ==================== Web Encryption ====================
|
|
888
909
|
async encrypt(text) {
|
|
889
910
|
if (typeof window === "undefined" || !window.crypto || !window.crypto.subtle) {
|
|
890
911
|
const encoder = new TextEncoder();
|
|
891
912
|
const data = encoder.encode(text);
|
|
892
|
-
return
|
|
913
|
+
return this.base64Encode(data);
|
|
893
914
|
}
|
|
894
915
|
try {
|
|
895
916
|
const encoder = new TextEncoder();
|
|
@@ -928,16 +949,12 @@ var ApiKeyStorage = class {
|
|
|
928
949
|
console.error("Web encryption failed:", error);
|
|
929
950
|
const encoder = new TextEncoder();
|
|
930
951
|
const data = encoder.encode(text);
|
|
931
|
-
return
|
|
952
|
+
return this.base64Encode(data);
|
|
932
953
|
}
|
|
933
954
|
}
|
|
934
955
|
async decrypt(encrypted) {
|
|
935
956
|
if (typeof window === "undefined" || !window.crypto || !window.crypto.subtle) {
|
|
936
|
-
const
|
|
937
|
-
const bytes = new Uint8Array(binary.length);
|
|
938
|
-
for (let i = 0; i < binary.length; i++) {
|
|
939
|
-
bytes[i] = binary.charCodeAt(i);
|
|
940
|
-
}
|
|
957
|
+
const bytes = this.base64Decode(encrypted);
|
|
941
958
|
const decoder = new TextDecoder();
|
|
942
959
|
return decoder.decode(bytes);
|
|
943
960
|
}
|
|
@@ -979,11 +996,7 @@ var ApiKeyStorage = class {
|
|
|
979
996
|
return decoder.decode(decrypted);
|
|
980
997
|
} catch (error) {
|
|
981
998
|
console.error("Web decryption failed:", error);
|
|
982
|
-
const
|
|
983
|
-
const bytes = new Uint8Array(binary.length);
|
|
984
|
-
for (let i = 0; i < binary.length; i++) {
|
|
985
|
-
bytes[i] = binary.charCodeAt(i);
|
|
986
|
-
}
|
|
999
|
+
const bytes = this.base64Decode(encrypted);
|
|
987
1000
|
const decoder = new TextDecoder();
|
|
988
1001
|
return decoder.decode(bytes);
|
|
989
1002
|
}
|
|
@@ -999,7 +1012,8 @@ var ApiKeyStorage = class {
|
|
|
999
1012
|
window.crypto.getRandomValues(buf);
|
|
1000
1013
|
raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1001
1014
|
} else {
|
|
1002
|
-
|
|
1015
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "node";
|
|
1016
|
+
raw = `${ua}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
|
|
1003
1017
|
}
|
|
1004
1018
|
if (typeof localStorage !== "undefined") {
|
|
1005
1019
|
localStorage.setItem(this.webEncryptionKeyStorage, raw);
|
|
@@ -1016,6 +1030,33 @@ var ApiKeyStorage = class {
|
|
|
1016
1030
|
isMobile() {
|
|
1017
1031
|
return typeof window !== "undefined" && window.SecureStorage !== void 0;
|
|
1018
1032
|
}
|
|
1033
|
+
base64Encode(bytes) {
|
|
1034
|
+
if (typeof btoa !== "undefined") {
|
|
1035
|
+
let binary = "";
|
|
1036
|
+
bytes.forEach((b) => {
|
|
1037
|
+
binary += String.fromCharCode(b);
|
|
1038
|
+
});
|
|
1039
|
+
return btoa(binary);
|
|
1040
|
+
}
|
|
1041
|
+
if (typeof Buffer !== "undefined") {
|
|
1042
|
+
return Buffer.from(bytes).toString("base64");
|
|
1043
|
+
}
|
|
1044
|
+
throw new Error("No base64 encoder available");
|
|
1045
|
+
}
|
|
1046
|
+
base64Decode(value) {
|
|
1047
|
+
if (typeof atob !== "undefined") {
|
|
1048
|
+
const binary = atob(value);
|
|
1049
|
+
const bytes = new Uint8Array(binary.length);
|
|
1050
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1051
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1052
|
+
}
|
|
1053
|
+
return bytes;
|
|
1054
|
+
}
|
|
1055
|
+
if (typeof Buffer !== "undefined") {
|
|
1056
|
+
return new Uint8Array(Buffer.from(value, "base64"));
|
|
1057
|
+
}
|
|
1058
|
+
throw new Error("No base64 decoder available");
|
|
1059
|
+
}
|
|
1019
1060
|
/**
|
|
1020
1061
|
* Normalize API keys to a SHA-256 hex digest.
|
|
1021
1062
|
* Accepts pre-hashed input and lowercases it to prevent double hashing.
|
|
@@ -1040,7 +1081,279 @@ var ApiKeyStorage = class {
|
|
|
1040
1081
|
}
|
|
1041
1082
|
};
|
|
1042
1083
|
|
|
1084
|
+
// src/storage/token-storage-web.ts
|
|
1085
|
+
var TokenStorageWeb = class {
|
|
1086
|
+
constructor() {
|
|
1087
|
+
this.storageKey = "lanonasis_mcp_tokens";
|
|
1088
|
+
this.webEncryptionKeyStorage = "lanonasis_web_token_enc_key";
|
|
1089
|
+
}
|
|
1090
|
+
async store(tokens) {
|
|
1091
|
+
const tokensWithTimestamp = {
|
|
1092
|
+
...tokens,
|
|
1093
|
+
issued_at: Date.now()
|
|
1094
|
+
};
|
|
1095
|
+
const tokenString = JSON.stringify(tokensWithTimestamp);
|
|
1096
|
+
const encrypted = await this.encrypt(tokenString);
|
|
1097
|
+
localStorage.setItem(this.storageKey, encrypted);
|
|
1098
|
+
}
|
|
1099
|
+
async retrieve() {
|
|
1100
|
+
const encrypted = localStorage.getItem(this.storageKey);
|
|
1101
|
+
if (!encrypted) return null;
|
|
1102
|
+
try {
|
|
1103
|
+
const tokenString = await this.decrypt(encrypted);
|
|
1104
|
+
return JSON.parse(tokenString);
|
|
1105
|
+
} catch {
|
|
1106
|
+
return null;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
async clear() {
|
|
1110
|
+
localStorage.removeItem(this.storageKey);
|
|
1111
|
+
}
|
|
1112
|
+
isTokenExpired(tokens) {
|
|
1113
|
+
if (tokens.token_type === "api-key" || tokens.expires_in === 0) return false;
|
|
1114
|
+
if (!tokens.expires_in) return false;
|
|
1115
|
+
if (!tokens.issued_at) return true;
|
|
1116
|
+
const expiresAt = tokens.issued_at + tokens.expires_in * 1e3;
|
|
1117
|
+
return expiresAt - Date.now() < 3e5;
|
|
1118
|
+
}
|
|
1119
|
+
async encrypt(text) {
|
|
1120
|
+
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
1121
|
+
const encoder2 = new TextEncoder();
|
|
1122
|
+
return this.base64Encode(encoder2.encode(text));
|
|
1123
|
+
}
|
|
1124
|
+
const encoder = new TextEncoder();
|
|
1125
|
+
const data = encoder.encode(text);
|
|
1126
|
+
const passphrase = await this.getWebEncryptionKey();
|
|
1127
|
+
const keyMaterial = await window.crypto.subtle.importKey(
|
|
1128
|
+
"raw",
|
|
1129
|
+
encoder.encode(passphrase),
|
|
1130
|
+
"PBKDF2",
|
|
1131
|
+
false,
|
|
1132
|
+
["deriveBits", "deriveKey"]
|
|
1133
|
+
);
|
|
1134
|
+
const key = await window.crypto.subtle.deriveKey(
|
|
1135
|
+
{
|
|
1136
|
+
name: "PBKDF2",
|
|
1137
|
+
salt: encoder.encode("lanonasis-token-salt"),
|
|
1138
|
+
iterations: 1e5,
|
|
1139
|
+
hash: "SHA-256"
|
|
1140
|
+
},
|
|
1141
|
+
keyMaterial,
|
|
1142
|
+
{ name: "AES-GCM", length: 256 },
|
|
1143
|
+
true,
|
|
1144
|
+
["encrypt", "decrypt"]
|
|
1145
|
+
);
|
|
1146
|
+
const iv = window.crypto.getRandomValues(new Uint8Array(12));
|
|
1147
|
+
const encrypted = await window.crypto.subtle.encrypt(
|
|
1148
|
+
{ name: "AES-GCM", iv },
|
|
1149
|
+
key,
|
|
1150
|
+
data
|
|
1151
|
+
);
|
|
1152
|
+
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
1153
|
+
combined.set(iv, 0);
|
|
1154
|
+
combined.set(new Uint8Array(encrypted), iv.length);
|
|
1155
|
+
return this.base64Encode(combined);
|
|
1156
|
+
}
|
|
1157
|
+
async decrypt(encrypted) {
|
|
1158
|
+
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
1159
|
+
const decoder2 = new TextDecoder();
|
|
1160
|
+
return decoder2.decode(this.base64Decode(encrypted));
|
|
1161
|
+
}
|
|
1162
|
+
const bytes = this.base64Decode(encrypted);
|
|
1163
|
+
const iv = bytes.slice(0, 12);
|
|
1164
|
+
const data = bytes.slice(12);
|
|
1165
|
+
const encoder = new TextEncoder();
|
|
1166
|
+
const decoder = new TextDecoder();
|
|
1167
|
+
const passphrase = await this.getWebEncryptionKey();
|
|
1168
|
+
const keyMaterial = await window.crypto.subtle.importKey(
|
|
1169
|
+
"raw",
|
|
1170
|
+
encoder.encode(passphrase),
|
|
1171
|
+
"PBKDF2",
|
|
1172
|
+
false,
|
|
1173
|
+
["deriveBits", "deriveKey"]
|
|
1174
|
+
);
|
|
1175
|
+
const key = await window.crypto.subtle.deriveKey(
|
|
1176
|
+
{
|
|
1177
|
+
name: "PBKDF2",
|
|
1178
|
+
salt: encoder.encode("lanonasis-token-salt"),
|
|
1179
|
+
iterations: 1e5,
|
|
1180
|
+
hash: "SHA-256"
|
|
1181
|
+
},
|
|
1182
|
+
keyMaterial,
|
|
1183
|
+
{ name: "AES-GCM", length: 256 },
|
|
1184
|
+
true,
|
|
1185
|
+
["encrypt", "decrypt"]
|
|
1186
|
+
);
|
|
1187
|
+
const decrypted = await window.crypto.subtle.decrypt(
|
|
1188
|
+
{ name: "AES-GCM", iv },
|
|
1189
|
+
key,
|
|
1190
|
+
data
|
|
1191
|
+
);
|
|
1192
|
+
return decoder.decode(decrypted);
|
|
1193
|
+
}
|
|
1194
|
+
async getWebEncryptionKey() {
|
|
1195
|
+
const existing = localStorage.getItem(this.webEncryptionKeyStorage);
|
|
1196
|
+
if (existing) return existing;
|
|
1197
|
+
const buf = new Uint8Array(32);
|
|
1198
|
+
if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
|
|
1199
|
+
window.crypto.getRandomValues(buf);
|
|
1200
|
+
}
|
|
1201
|
+
const raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1202
|
+
localStorage.setItem(this.webEncryptionKeyStorage, raw);
|
|
1203
|
+
return raw;
|
|
1204
|
+
}
|
|
1205
|
+
base64Encode(bytes) {
|
|
1206
|
+
let binary = "";
|
|
1207
|
+
bytes.forEach((b) => {
|
|
1208
|
+
binary += String.fromCharCode(b);
|
|
1209
|
+
});
|
|
1210
|
+
return btoa(binary);
|
|
1211
|
+
}
|
|
1212
|
+
base64Decode(value) {
|
|
1213
|
+
const binary = atob(value);
|
|
1214
|
+
const bytes = new Uint8Array(binary.length);
|
|
1215
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1216
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1217
|
+
}
|
|
1218
|
+
return bytes;
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1222
|
+
// src/storage/api-key-storage-web.ts
|
|
1223
|
+
var ApiKeyStorageWeb = class {
|
|
1224
|
+
constructor() {
|
|
1225
|
+
this.storageKey = "lanonasis_api_key";
|
|
1226
|
+
this.webEncryptionKeyStorage = "lanonasis_web_enc_key";
|
|
1227
|
+
}
|
|
1228
|
+
async store(data) {
|
|
1229
|
+
const payload = JSON.stringify({
|
|
1230
|
+
...data,
|
|
1231
|
+
createdAt: data.createdAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
1232
|
+
});
|
|
1233
|
+
const encrypted = await this.encrypt(payload);
|
|
1234
|
+
localStorage.setItem(this.storageKey, encrypted);
|
|
1235
|
+
}
|
|
1236
|
+
async retrieve() {
|
|
1237
|
+
const encrypted = localStorage.getItem(this.storageKey);
|
|
1238
|
+
if (!encrypted) return null;
|
|
1239
|
+
try {
|
|
1240
|
+
const decrypted = await this.decrypt(encrypted);
|
|
1241
|
+
return JSON.parse(decrypted);
|
|
1242
|
+
} catch {
|
|
1243
|
+
return null;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
async clear() {
|
|
1247
|
+
localStorage.removeItem(this.storageKey);
|
|
1248
|
+
}
|
|
1249
|
+
async encrypt(text) {
|
|
1250
|
+
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
1251
|
+
const encoder2 = new TextEncoder();
|
|
1252
|
+
return this.base64Encode(encoder2.encode(text));
|
|
1253
|
+
}
|
|
1254
|
+
const encoder = new TextEncoder();
|
|
1255
|
+
const data = encoder.encode(text);
|
|
1256
|
+
const passphrase = await this.getWebEncryptionKey();
|
|
1257
|
+
const keyMaterial = await window.crypto.subtle.importKey(
|
|
1258
|
+
"raw",
|
|
1259
|
+
encoder.encode(passphrase),
|
|
1260
|
+
"PBKDF2",
|
|
1261
|
+
false,
|
|
1262
|
+
["deriveBits", "deriveKey"]
|
|
1263
|
+
);
|
|
1264
|
+
const key = await window.crypto.subtle.deriveKey(
|
|
1265
|
+
{
|
|
1266
|
+
name: "PBKDF2",
|
|
1267
|
+
salt: encoder.encode("lanonasis-api-key-salt"),
|
|
1268
|
+
iterations: 1e5,
|
|
1269
|
+
hash: "SHA-256"
|
|
1270
|
+
},
|
|
1271
|
+
keyMaterial,
|
|
1272
|
+
{ name: "AES-GCM", length: 256 },
|
|
1273
|
+
true,
|
|
1274
|
+
["encrypt", "decrypt"]
|
|
1275
|
+
);
|
|
1276
|
+
const iv = window.crypto.getRandomValues(new Uint8Array(12));
|
|
1277
|
+
const encrypted = await window.crypto.subtle.encrypt(
|
|
1278
|
+
{ name: "AES-GCM", iv },
|
|
1279
|
+
key,
|
|
1280
|
+
data
|
|
1281
|
+
);
|
|
1282
|
+
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
1283
|
+
combined.set(iv, 0);
|
|
1284
|
+
combined.set(new Uint8Array(encrypted), iv.length);
|
|
1285
|
+
return this.base64Encode(combined);
|
|
1286
|
+
}
|
|
1287
|
+
async decrypt(encrypted) {
|
|
1288
|
+
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
1289
|
+
const decoder2 = new TextDecoder();
|
|
1290
|
+
return decoder2.decode(this.base64Decode(encrypted));
|
|
1291
|
+
}
|
|
1292
|
+
const bytes = this.base64Decode(encrypted);
|
|
1293
|
+
const iv = bytes.slice(0, 12);
|
|
1294
|
+
const data = bytes.slice(12);
|
|
1295
|
+
const encoder = new TextEncoder();
|
|
1296
|
+
const decoder = new TextDecoder();
|
|
1297
|
+
const passphrase = await this.getWebEncryptionKey();
|
|
1298
|
+
const keyMaterial = await window.crypto.subtle.importKey(
|
|
1299
|
+
"raw",
|
|
1300
|
+
encoder.encode(passphrase),
|
|
1301
|
+
"PBKDF2",
|
|
1302
|
+
false,
|
|
1303
|
+
["deriveBits", "deriveKey"]
|
|
1304
|
+
);
|
|
1305
|
+
const key = await window.crypto.subtle.deriveKey(
|
|
1306
|
+
{
|
|
1307
|
+
name: "PBKDF2",
|
|
1308
|
+
salt: encoder.encode("lanonasis-api-key-salt"),
|
|
1309
|
+
iterations: 1e5,
|
|
1310
|
+
hash: "SHA-256"
|
|
1311
|
+
},
|
|
1312
|
+
keyMaterial,
|
|
1313
|
+
{ name: "AES-GCM", length: 256 },
|
|
1314
|
+
true,
|
|
1315
|
+
["encrypt", "decrypt"]
|
|
1316
|
+
);
|
|
1317
|
+
const decrypted = await window.crypto.subtle.decrypt(
|
|
1318
|
+
{ name: "AES-GCM", iv },
|
|
1319
|
+
key,
|
|
1320
|
+
data
|
|
1321
|
+
);
|
|
1322
|
+
return decoder.decode(decrypted);
|
|
1323
|
+
}
|
|
1324
|
+
async getWebEncryptionKey() {
|
|
1325
|
+
const existing = localStorage.getItem(this.webEncryptionKeyStorage);
|
|
1326
|
+
if (existing) return existing;
|
|
1327
|
+
const buf = new Uint8Array(32);
|
|
1328
|
+
if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
|
|
1329
|
+
window.crypto.getRandomValues(buf);
|
|
1330
|
+
}
|
|
1331
|
+
const raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1332
|
+
localStorage.setItem(this.webEncryptionKeyStorage, raw);
|
|
1333
|
+
return raw;
|
|
1334
|
+
}
|
|
1335
|
+
base64Encode(bytes) {
|
|
1336
|
+
let binary = "";
|
|
1337
|
+
bytes.forEach((b) => {
|
|
1338
|
+
binary += String.fromCharCode(b);
|
|
1339
|
+
});
|
|
1340
|
+
return btoa(binary);
|
|
1341
|
+
}
|
|
1342
|
+
base64Decode(value) {
|
|
1343
|
+
const binary = atob(value);
|
|
1344
|
+
const bytes = new Uint8Array(binary.length);
|
|
1345
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1346
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1347
|
+
}
|
|
1348
|
+
return bytes;
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
|
|
1352
|
+
// src/client/mcp-client.ts
|
|
1353
|
+
import fetch4 from "cross-fetch";
|
|
1354
|
+
|
|
1043
1355
|
// src/flows/apikey-flow.ts
|
|
1356
|
+
import fetch3 from "cross-fetch";
|
|
1044
1357
|
var APIKeyFlow = class extends BaseOAuthFlow {
|
|
1045
1358
|
constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
|
|
1046
1359
|
super({
|
|
@@ -1083,7 +1396,7 @@ var APIKeyFlow = class extends BaseOAuthFlow {
|
|
|
1083
1396
|
*/
|
|
1084
1397
|
async validateAPIKey() {
|
|
1085
1398
|
try {
|
|
1086
|
-
const response = await
|
|
1399
|
+
const response = await fetch3(`${this.config.authBaseUrl}/api/v1/health`, {
|
|
1087
1400
|
headers: {
|
|
1088
1401
|
"x-api-key": this.apiKey
|
|
1089
1402
|
}
|
|
@@ -1109,7 +1422,8 @@ var MCPClient = class {
|
|
|
1109
1422
|
autoRefresh: true,
|
|
1110
1423
|
...config
|
|
1111
1424
|
};
|
|
1112
|
-
|
|
1425
|
+
const defaultStorage = typeof window !== "undefined" ? new TokenStorageWeb() : new TokenStorage();
|
|
1426
|
+
this.tokenStorage = config.tokenStorage || defaultStorage;
|
|
1113
1427
|
this.authMode = config.apiKey ? "apikey" : "oauth";
|
|
1114
1428
|
if (this.authMode === "apikey") {
|
|
1115
1429
|
this.authFlow = new APIKeyFlow(
|
|
@@ -1324,7 +1638,7 @@ var MCPClient = class {
|
|
|
1324
1638
|
} else {
|
|
1325
1639
|
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
1326
1640
|
}
|
|
1327
|
-
const response = await
|
|
1641
|
+
const response = await fetch4(`${this.config.mcpEndpoint}/api`, {
|
|
1328
1642
|
method: "POST",
|
|
1329
1643
|
headers,
|
|
1330
1644
|
body: JSON.stringify({
|
|
@@ -1425,9 +1739,11 @@ var MCPClient = class {
|
|
|
1425
1739
|
};
|
|
1426
1740
|
export {
|
|
1427
1741
|
ApiKeyStorage,
|
|
1742
|
+
ApiKeyStorageWeb,
|
|
1428
1743
|
BaseOAuthFlow,
|
|
1429
1744
|
DesktopOAuthFlow,
|
|
1430
1745
|
MCPClient,
|
|
1431
1746
|
TerminalOAuthFlow,
|
|
1432
|
-
TokenStorage
|
|
1747
|
+
TokenStorage,
|
|
1748
|
+
TokenStorageWeb
|
|
1433
1749
|
};
|