@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
package/dist/index.cjs
CHANGED
|
@@ -31,23 +31,29 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
ApiKeyStorage: () => ApiKeyStorage,
|
|
34
|
+
ApiKeyStorageWeb: () => ApiKeyStorageWeb,
|
|
34
35
|
BaseOAuthFlow: () => BaseOAuthFlow,
|
|
35
36
|
DesktopOAuthFlow: () => DesktopOAuthFlow,
|
|
36
37
|
MCPClient: () => MCPClient,
|
|
37
38
|
TerminalOAuthFlow: () => TerminalOAuthFlow,
|
|
38
|
-
TokenStorage: () => TokenStorage
|
|
39
|
+
TokenStorage: () => TokenStorage,
|
|
40
|
+
TokenStorageWeb: () => TokenStorageWeb
|
|
39
41
|
});
|
|
40
42
|
module.exports = __toCommonJS(index_exports);
|
|
41
43
|
|
|
44
|
+
// src/flows/terminal-flow.ts
|
|
45
|
+
var import_cross_fetch2 = __toESM(require("cross-fetch"), 1);
|
|
46
|
+
|
|
42
47
|
// src/flows/base-flow.ts
|
|
48
|
+
var import_cross_fetch = __toESM(require("cross-fetch"), 1);
|
|
43
49
|
var BaseOAuthFlow = class {
|
|
44
50
|
constructor(config) {
|
|
45
51
|
this.clientId = config.clientId;
|
|
46
52
|
this.authBaseUrl = config.authBaseUrl || "https://auth.lanonasis.com";
|
|
47
|
-
this.scope = config.scope || "
|
|
53
|
+
this.scope = config.scope || "memories:read memories:write memories:delete profile";
|
|
48
54
|
}
|
|
49
55
|
async makeTokenRequest(body) {
|
|
50
|
-
const response = await
|
|
56
|
+
const response = await (0, import_cross_fetch.default)(`${this.authBaseUrl}/oauth/token`, {
|
|
51
57
|
method: "POST",
|
|
52
58
|
headers: { "Content-Type": "application/json" },
|
|
53
59
|
body: JSON.stringify(body)
|
|
@@ -60,11 +66,10 @@ var BaseOAuthFlow = class {
|
|
|
60
66
|
}
|
|
61
67
|
generateState() {
|
|
62
68
|
const array = new Uint8Array(32);
|
|
63
|
-
if (typeof
|
|
64
|
-
|
|
69
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
70
|
+
crypto.getRandomValues(array);
|
|
65
71
|
} else {
|
|
66
|
-
|
|
67
|
-
crypto.randomFillSync(array);
|
|
72
|
+
throw new Error("Secure random generation is not available");
|
|
68
73
|
}
|
|
69
74
|
return this.base64URLEncode(array);
|
|
70
75
|
}
|
|
@@ -74,7 +79,8 @@ var BaseOAuthFlow = class {
|
|
|
74
79
|
bytes.forEach((byte) => {
|
|
75
80
|
binary += String.fromCharCode(byte);
|
|
76
81
|
});
|
|
77
|
-
|
|
82
|
+
const base64 = typeof btoa !== "undefined" ? btoa(binary) : Buffer.from(bytes).toString("base64");
|
|
83
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
78
84
|
}
|
|
79
85
|
async refreshToken(refreshToken) {
|
|
80
86
|
return this.makeTokenRequest({
|
|
@@ -84,7 +90,7 @@ var BaseOAuthFlow = class {
|
|
|
84
90
|
});
|
|
85
91
|
}
|
|
86
92
|
async revokeToken(token, tokenType = "access_token") {
|
|
87
|
-
const response = await
|
|
93
|
+
const response = await (0, import_cross_fetch.default)(`${this.authBaseUrl}/oauth/revoke`, {
|
|
88
94
|
method: "POST",
|
|
89
95
|
headers: { "Content-Type": "application/json" },
|
|
90
96
|
body: JSON.stringify({
|
|
@@ -100,7 +106,6 @@ var BaseOAuthFlow = class {
|
|
|
100
106
|
};
|
|
101
107
|
|
|
102
108
|
// src/flows/terminal-flow.ts
|
|
103
|
-
var import_open = __toESM(require("open"), 1);
|
|
104
109
|
var TerminalOAuthFlow = class extends BaseOAuthFlow {
|
|
105
110
|
constructor(config) {
|
|
106
111
|
super({
|
|
@@ -123,7 +128,7 @@ var TerminalOAuthFlow = class extends BaseOAuthFlow {
|
|
|
123
128
|
}
|
|
124
129
|
}
|
|
125
130
|
async requestDeviceCode() {
|
|
126
|
-
const response = await
|
|
131
|
+
const response = await (0, import_cross_fetch2.default)(`${this.authBaseUrl}/oauth/device`, {
|
|
127
132
|
method: "POST",
|
|
128
133
|
headers: { "Content-Type": "application/json" },
|
|
129
134
|
body: JSON.stringify({
|
|
@@ -148,12 +153,13 @@ var TerminalOAuthFlow = class extends BaseOAuthFlow {
|
|
|
148
153
|
}
|
|
149
154
|
async openBrowser(url) {
|
|
150
155
|
try {
|
|
156
|
+
const { default: open } = await import("open");
|
|
151
157
|
await Promise.race([
|
|
152
158
|
this.waitForEnter(),
|
|
153
159
|
new Promise((resolve) => setTimeout(resolve, 2e3))
|
|
154
160
|
]);
|
|
155
161
|
console.log("Opening browser...");
|
|
156
|
-
await (
|
|
162
|
+
await open(url);
|
|
157
163
|
} catch (error) {
|
|
158
164
|
console.log("Please open the URL manually in your browser.");
|
|
159
165
|
}
|
|
@@ -196,7 +202,7 @@ var TerminalOAuthFlow = class extends BaseOAuthFlow {
|
|
|
196
202
|
throw new Error("Authorization timeout - please try again");
|
|
197
203
|
}
|
|
198
204
|
async checkDeviceCode(deviceCode) {
|
|
199
|
-
const response = await
|
|
205
|
+
const response = await (0, import_cross_fetch2.default)(`${this.authBaseUrl}/oauth/token`, {
|
|
200
206
|
method: "POST",
|
|
201
207
|
headers: { "Content-Type": "application/json" },
|
|
202
208
|
body: JSON.stringify({
|
|
@@ -237,25 +243,22 @@ var DesktopOAuthFlow = class extends BaseOAuthFlow {
|
|
|
237
243
|
}
|
|
238
244
|
generateCodeVerifier() {
|
|
239
245
|
const array = new Uint8Array(32);
|
|
240
|
-
if (typeof
|
|
241
|
-
|
|
246
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
247
|
+
crypto.getRandomValues(array);
|
|
242
248
|
} else {
|
|
243
|
-
|
|
244
|
-
crypto.randomFillSync(array);
|
|
249
|
+
throw new Error("Secure random generation is not available in this environment");
|
|
245
250
|
}
|
|
246
251
|
return this.base64URLEncode(array);
|
|
247
252
|
}
|
|
248
253
|
async generateCodeChallenge(verifier) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const hash = await window.crypto.subtle.digest("SHA-256", data);
|
|
253
|
-
return this.base64URLEncode(hash);
|
|
254
|
-
} else {
|
|
255
|
-
const crypto = require("crypto");
|
|
256
|
-
const hash = crypto.createHash("sha256").update(verifier).digest();
|
|
257
|
-
return this.base64URLEncode(hash);
|
|
254
|
+
const subtle = typeof crypto !== "undefined" ? crypto.subtle : void 0;
|
|
255
|
+
if (!subtle) {
|
|
256
|
+
throw new Error("Web Crypto is required to generate PKCE code challenge");
|
|
258
257
|
}
|
|
258
|
+
const encoder = new TextEncoder();
|
|
259
|
+
const data = encoder.encode(verifier);
|
|
260
|
+
const hash = await subtle.digest("SHA-256", data);
|
|
261
|
+
return this.base64URLEncode(hash);
|
|
259
262
|
}
|
|
260
263
|
buildAuthorizationUrl(codeChallenge, state) {
|
|
261
264
|
const params = new URLSearchParams({
|
|
@@ -457,13 +460,13 @@ var TokenStorage = class {
|
|
|
457
460
|
const fs = require("fs").promises;
|
|
458
461
|
const path = require("path");
|
|
459
462
|
const os = require("os");
|
|
460
|
-
const
|
|
463
|
+
const crypto2 = require("crypto");
|
|
461
464
|
const configDir = path.join(os.homedir(), ".lanonasis");
|
|
462
465
|
const tokenFile = path.join(configDir, "mcp-tokens.enc");
|
|
463
466
|
await fs.mkdir(configDir, { recursive: true });
|
|
464
467
|
const key = this.getFileEncryptionKey();
|
|
465
|
-
const iv =
|
|
466
|
-
const cipher =
|
|
468
|
+
const iv = crypto2.randomBytes(16);
|
|
469
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
|
|
467
470
|
let encrypted = cipher.update(tokenString, "utf8", "hex");
|
|
468
471
|
encrypted += cipher.final("hex");
|
|
469
472
|
const authTag = cipher.getAuthTag().toString("hex");
|
|
@@ -475,7 +478,7 @@ var TokenStorage = class {
|
|
|
475
478
|
const fs = require("fs").promises;
|
|
476
479
|
const path = require("path");
|
|
477
480
|
const os = require("os");
|
|
478
|
-
const
|
|
481
|
+
const crypto2 = require("crypto");
|
|
479
482
|
const tokenFile = path.join(os.homedir(), ".lanonasis", "mcp-tokens.enc");
|
|
480
483
|
try {
|
|
481
484
|
const data = await fs.readFile(tokenFile, "utf8");
|
|
@@ -485,7 +488,7 @@ var TokenStorage = class {
|
|
|
485
488
|
const [ivHex, authTagHex, encrypted] = parts;
|
|
486
489
|
const iv = Buffer.from(ivHex, "hex");
|
|
487
490
|
const authTag = Buffer.from(authTagHex, "hex");
|
|
488
|
-
const decipher =
|
|
491
|
+
const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
|
|
489
492
|
decipher.setAuthTag(authTag);
|
|
490
493
|
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
491
494
|
decrypted += decipher.final("utf8");
|
|
@@ -494,7 +497,7 @@ var TokenStorage = class {
|
|
|
494
497
|
if (parts.length === 2) {
|
|
495
498
|
const [ivHex, encrypted] = parts;
|
|
496
499
|
const iv = Buffer.from(ivHex, "hex");
|
|
497
|
-
const decipher =
|
|
500
|
+
const decipher = crypto2.createDecipheriv("aes-256-cbc", key, iv);
|
|
498
501
|
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
499
502
|
decrypted += decipher.final("utf8");
|
|
500
503
|
return decrypted;
|
|
@@ -516,17 +519,17 @@ var TokenStorage = class {
|
|
|
516
519
|
}
|
|
517
520
|
}
|
|
518
521
|
getFileEncryptionKey() {
|
|
519
|
-
const
|
|
522
|
+
const crypto2 = require("crypto");
|
|
520
523
|
const os = require("os");
|
|
521
524
|
const machineId = os.hostname() + os.userInfo().username;
|
|
522
525
|
const salt = "lanonasis-mcp-oauth-2024";
|
|
523
|
-
return
|
|
526
|
+
return crypto2.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
|
|
524
527
|
}
|
|
525
528
|
async encrypt(text) {
|
|
526
529
|
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
527
530
|
const encoder2 = new TextEncoder();
|
|
528
531
|
const data2 = encoder2.encode(text);
|
|
529
|
-
return
|
|
532
|
+
return this.base64Encode(data2);
|
|
530
533
|
}
|
|
531
534
|
const encoder = new TextEncoder();
|
|
532
535
|
const data = encoder.encode(text);
|
|
@@ -559,23 +562,15 @@ var TokenStorage = class {
|
|
|
559
562
|
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
560
563
|
combined.set(iv, 0);
|
|
561
564
|
combined.set(new Uint8Array(encrypted), iv.length);
|
|
562
|
-
return
|
|
565
|
+
return this.base64Encode(combined);
|
|
563
566
|
}
|
|
564
567
|
async decrypt(encrypted) {
|
|
565
568
|
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
566
|
-
const
|
|
567
|
-
const bytes2 = new Uint8Array(binary2.length);
|
|
568
|
-
for (let i = 0; i < binary2.length; i++) {
|
|
569
|
-
bytes2[i] = binary2.charCodeAt(i);
|
|
570
|
-
}
|
|
569
|
+
const bytes2 = this.base64Decode(encrypted);
|
|
571
570
|
const decoder2 = new TextDecoder();
|
|
572
571
|
return decoder2.decode(bytes2);
|
|
573
572
|
}
|
|
574
|
-
const
|
|
575
|
-
const bytes = new Uint8Array(binary.length);
|
|
576
|
-
for (let i = 0; i < binary.length; i++) {
|
|
577
|
-
bytes[i] = binary.charCodeAt(i);
|
|
578
|
-
}
|
|
573
|
+
const bytes = this.base64Decode(encrypted);
|
|
579
574
|
const iv = bytes.slice(0, 12);
|
|
580
575
|
const data = bytes.slice(12);
|
|
581
576
|
const encoder = new TextEncoder();
|
|
@@ -616,6 +611,33 @@ var TokenStorage = class {
|
|
|
616
611
|
isMobile() {
|
|
617
612
|
return typeof window !== "undefined" && window.SecureStorage !== void 0;
|
|
618
613
|
}
|
|
614
|
+
base64Encode(bytes) {
|
|
615
|
+
if (typeof btoa !== "undefined") {
|
|
616
|
+
let binary = "";
|
|
617
|
+
bytes.forEach((b) => {
|
|
618
|
+
binary += String.fromCharCode(b);
|
|
619
|
+
});
|
|
620
|
+
return btoa(binary);
|
|
621
|
+
}
|
|
622
|
+
if (typeof Buffer !== "undefined") {
|
|
623
|
+
return Buffer.from(bytes).toString("base64");
|
|
624
|
+
}
|
|
625
|
+
throw new Error("No base64 encoder available");
|
|
626
|
+
}
|
|
627
|
+
base64Decode(value) {
|
|
628
|
+
if (typeof atob !== "undefined") {
|
|
629
|
+
const binary = atob(value);
|
|
630
|
+
const bytes = new Uint8Array(binary.length);
|
|
631
|
+
for (let i = 0; i < binary.length; i++) {
|
|
632
|
+
bytes[i] = binary.charCodeAt(i);
|
|
633
|
+
}
|
|
634
|
+
return bytes;
|
|
635
|
+
}
|
|
636
|
+
if (typeof Buffer !== "undefined") {
|
|
637
|
+
return new Uint8Array(Buffer.from(value, "base64"));
|
|
638
|
+
}
|
|
639
|
+
throw new Error("No base64 decoder available");
|
|
640
|
+
}
|
|
619
641
|
async getWebEncryptionKey() {
|
|
620
642
|
const existing = typeof localStorage !== "undefined" ? localStorage.getItem(this.webEncryptionKeyStorage) : null;
|
|
621
643
|
if (existing) {
|
|
@@ -627,7 +649,8 @@ var TokenStorage = class {
|
|
|
627
649
|
window.crypto.getRandomValues(buf);
|
|
628
650
|
raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
629
651
|
} else {
|
|
630
|
-
|
|
652
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "node";
|
|
653
|
+
raw = `${ua}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
|
|
631
654
|
}
|
|
632
655
|
if (typeof localStorage !== "undefined") {
|
|
633
656
|
localStorage.setItem(this.webEncryptionKeyStorage, raw);
|
|
@@ -839,13 +862,13 @@ var ApiKeyStorage = class {
|
|
|
839
862
|
const fs = require("fs").promises;
|
|
840
863
|
const path = require("path");
|
|
841
864
|
const os = require("os");
|
|
842
|
-
const
|
|
865
|
+
const crypto2 = require("crypto");
|
|
843
866
|
const configDir = path.join(os.homedir(), ".lanonasis");
|
|
844
867
|
const keyFile = path.join(configDir, "api-key.enc");
|
|
845
868
|
await fs.mkdir(configDir, { recursive: true, mode: 448 });
|
|
846
869
|
const key = this.getFileEncryptionKey();
|
|
847
|
-
const iv =
|
|
848
|
-
const cipher =
|
|
870
|
+
const iv = crypto2.randomBytes(16);
|
|
871
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
|
|
849
872
|
let encrypted = cipher.update(keyString, "utf8", "hex");
|
|
850
873
|
encrypted += cipher.final("hex");
|
|
851
874
|
const authTag = cipher.getAuthTag();
|
|
@@ -857,7 +880,7 @@ var ApiKeyStorage = class {
|
|
|
857
880
|
const fs = require("fs").promises;
|
|
858
881
|
const path = require("path");
|
|
859
882
|
const os = require("os");
|
|
860
|
-
const
|
|
883
|
+
const crypto2 = require("crypto");
|
|
861
884
|
const keyFile = path.join(os.homedir(), ".lanonasis", "api-key.enc");
|
|
862
885
|
try {
|
|
863
886
|
const data = await fs.readFile(keyFile, "utf8");
|
|
@@ -868,7 +891,7 @@ var ApiKeyStorage = class {
|
|
|
868
891
|
const key = this.getFileEncryptionKey();
|
|
869
892
|
const iv = Buffer.from(ivHex, "hex");
|
|
870
893
|
const authTag = Buffer.from(authTagHex, "hex");
|
|
871
|
-
const decipher =
|
|
894
|
+
const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
|
|
872
895
|
decipher.setAuthTag(authTag);
|
|
873
896
|
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
874
897
|
decrypted += decipher.final("utf8");
|
|
@@ -912,18 +935,18 @@ var ApiKeyStorage = class {
|
|
|
912
935
|
}
|
|
913
936
|
}
|
|
914
937
|
getFileEncryptionKey() {
|
|
915
|
-
const
|
|
938
|
+
const crypto2 = require("crypto");
|
|
916
939
|
const os = require("os");
|
|
917
940
|
const machineId = os.hostname() + os.userInfo().username;
|
|
918
941
|
const salt = "lanonasis-mcp-api-key-2024";
|
|
919
|
-
return
|
|
942
|
+
return crypto2.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
|
|
920
943
|
}
|
|
921
944
|
// ==================== Web Encryption ====================
|
|
922
945
|
async encrypt(text) {
|
|
923
946
|
if (typeof window === "undefined" || !window.crypto || !window.crypto.subtle) {
|
|
924
947
|
const encoder = new TextEncoder();
|
|
925
948
|
const data = encoder.encode(text);
|
|
926
|
-
return
|
|
949
|
+
return this.base64Encode(data);
|
|
927
950
|
}
|
|
928
951
|
try {
|
|
929
952
|
const encoder = new TextEncoder();
|
|
@@ -962,16 +985,12 @@ var ApiKeyStorage = class {
|
|
|
962
985
|
console.error("Web encryption failed:", error);
|
|
963
986
|
const encoder = new TextEncoder();
|
|
964
987
|
const data = encoder.encode(text);
|
|
965
|
-
return
|
|
988
|
+
return this.base64Encode(data);
|
|
966
989
|
}
|
|
967
990
|
}
|
|
968
991
|
async decrypt(encrypted) {
|
|
969
992
|
if (typeof window === "undefined" || !window.crypto || !window.crypto.subtle) {
|
|
970
|
-
const
|
|
971
|
-
const bytes = new Uint8Array(binary.length);
|
|
972
|
-
for (let i = 0; i < binary.length; i++) {
|
|
973
|
-
bytes[i] = binary.charCodeAt(i);
|
|
974
|
-
}
|
|
993
|
+
const bytes = this.base64Decode(encrypted);
|
|
975
994
|
const decoder = new TextDecoder();
|
|
976
995
|
return decoder.decode(bytes);
|
|
977
996
|
}
|
|
@@ -1013,11 +1032,7 @@ var ApiKeyStorage = class {
|
|
|
1013
1032
|
return decoder.decode(decrypted);
|
|
1014
1033
|
} catch (error) {
|
|
1015
1034
|
console.error("Web decryption failed:", error);
|
|
1016
|
-
const
|
|
1017
|
-
const bytes = new Uint8Array(binary.length);
|
|
1018
|
-
for (let i = 0; i < binary.length; i++) {
|
|
1019
|
-
bytes[i] = binary.charCodeAt(i);
|
|
1020
|
-
}
|
|
1035
|
+
const bytes = this.base64Decode(encrypted);
|
|
1021
1036
|
const decoder = new TextDecoder();
|
|
1022
1037
|
return decoder.decode(bytes);
|
|
1023
1038
|
}
|
|
@@ -1033,7 +1048,8 @@ var ApiKeyStorage = class {
|
|
|
1033
1048
|
window.crypto.getRandomValues(buf);
|
|
1034
1049
|
raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1035
1050
|
} else {
|
|
1036
|
-
|
|
1051
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "node";
|
|
1052
|
+
raw = `${ua}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
|
|
1037
1053
|
}
|
|
1038
1054
|
if (typeof localStorage !== "undefined") {
|
|
1039
1055
|
localStorage.setItem(this.webEncryptionKeyStorage, raw);
|
|
@@ -1050,6 +1066,33 @@ var ApiKeyStorage = class {
|
|
|
1050
1066
|
isMobile() {
|
|
1051
1067
|
return typeof window !== "undefined" && window.SecureStorage !== void 0;
|
|
1052
1068
|
}
|
|
1069
|
+
base64Encode(bytes) {
|
|
1070
|
+
if (typeof btoa !== "undefined") {
|
|
1071
|
+
let binary = "";
|
|
1072
|
+
bytes.forEach((b) => {
|
|
1073
|
+
binary += String.fromCharCode(b);
|
|
1074
|
+
});
|
|
1075
|
+
return btoa(binary);
|
|
1076
|
+
}
|
|
1077
|
+
if (typeof Buffer !== "undefined") {
|
|
1078
|
+
return Buffer.from(bytes).toString("base64");
|
|
1079
|
+
}
|
|
1080
|
+
throw new Error("No base64 encoder available");
|
|
1081
|
+
}
|
|
1082
|
+
base64Decode(value) {
|
|
1083
|
+
if (typeof atob !== "undefined") {
|
|
1084
|
+
const binary = atob(value);
|
|
1085
|
+
const bytes = new Uint8Array(binary.length);
|
|
1086
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1087
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1088
|
+
}
|
|
1089
|
+
return bytes;
|
|
1090
|
+
}
|
|
1091
|
+
if (typeof Buffer !== "undefined") {
|
|
1092
|
+
return new Uint8Array(Buffer.from(value, "base64"));
|
|
1093
|
+
}
|
|
1094
|
+
throw new Error("No base64 decoder available");
|
|
1095
|
+
}
|
|
1053
1096
|
/**
|
|
1054
1097
|
* Normalize API keys to a SHA-256 hex digest.
|
|
1055
1098
|
* Accepts pre-hashed input and lowercases it to prevent double hashing.
|
|
@@ -1074,7 +1117,279 @@ var ApiKeyStorage = class {
|
|
|
1074
1117
|
}
|
|
1075
1118
|
};
|
|
1076
1119
|
|
|
1120
|
+
// src/storage/token-storage-web.ts
|
|
1121
|
+
var TokenStorageWeb = class {
|
|
1122
|
+
constructor() {
|
|
1123
|
+
this.storageKey = "lanonasis_mcp_tokens";
|
|
1124
|
+
this.webEncryptionKeyStorage = "lanonasis_web_token_enc_key";
|
|
1125
|
+
}
|
|
1126
|
+
async store(tokens) {
|
|
1127
|
+
const tokensWithTimestamp = {
|
|
1128
|
+
...tokens,
|
|
1129
|
+
issued_at: Date.now()
|
|
1130
|
+
};
|
|
1131
|
+
const tokenString = JSON.stringify(tokensWithTimestamp);
|
|
1132
|
+
const encrypted = await this.encrypt(tokenString);
|
|
1133
|
+
localStorage.setItem(this.storageKey, encrypted);
|
|
1134
|
+
}
|
|
1135
|
+
async retrieve() {
|
|
1136
|
+
const encrypted = localStorage.getItem(this.storageKey);
|
|
1137
|
+
if (!encrypted) return null;
|
|
1138
|
+
try {
|
|
1139
|
+
const tokenString = await this.decrypt(encrypted);
|
|
1140
|
+
return JSON.parse(tokenString);
|
|
1141
|
+
} catch {
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
async clear() {
|
|
1146
|
+
localStorage.removeItem(this.storageKey);
|
|
1147
|
+
}
|
|
1148
|
+
isTokenExpired(tokens) {
|
|
1149
|
+
if (tokens.token_type === "api-key" || tokens.expires_in === 0) return false;
|
|
1150
|
+
if (!tokens.expires_in) return false;
|
|
1151
|
+
if (!tokens.issued_at) return true;
|
|
1152
|
+
const expiresAt = tokens.issued_at + tokens.expires_in * 1e3;
|
|
1153
|
+
return expiresAt - Date.now() < 3e5;
|
|
1154
|
+
}
|
|
1155
|
+
async encrypt(text) {
|
|
1156
|
+
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
1157
|
+
const encoder2 = new TextEncoder();
|
|
1158
|
+
return this.base64Encode(encoder2.encode(text));
|
|
1159
|
+
}
|
|
1160
|
+
const encoder = new TextEncoder();
|
|
1161
|
+
const data = encoder.encode(text);
|
|
1162
|
+
const passphrase = await this.getWebEncryptionKey();
|
|
1163
|
+
const keyMaterial = await window.crypto.subtle.importKey(
|
|
1164
|
+
"raw",
|
|
1165
|
+
encoder.encode(passphrase),
|
|
1166
|
+
"PBKDF2",
|
|
1167
|
+
false,
|
|
1168
|
+
["deriveBits", "deriveKey"]
|
|
1169
|
+
);
|
|
1170
|
+
const key = await window.crypto.subtle.deriveKey(
|
|
1171
|
+
{
|
|
1172
|
+
name: "PBKDF2",
|
|
1173
|
+
salt: encoder.encode("lanonasis-token-salt"),
|
|
1174
|
+
iterations: 1e5,
|
|
1175
|
+
hash: "SHA-256"
|
|
1176
|
+
},
|
|
1177
|
+
keyMaterial,
|
|
1178
|
+
{ name: "AES-GCM", length: 256 },
|
|
1179
|
+
true,
|
|
1180
|
+
["encrypt", "decrypt"]
|
|
1181
|
+
);
|
|
1182
|
+
const iv = window.crypto.getRandomValues(new Uint8Array(12));
|
|
1183
|
+
const encrypted = await window.crypto.subtle.encrypt(
|
|
1184
|
+
{ name: "AES-GCM", iv },
|
|
1185
|
+
key,
|
|
1186
|
+
data
|
|
1187
|
+
);
|
|
1188
|
+
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
1189
|
+
combined.set(iv, 0);
|
|
1190
|
+
combined.set(new Uint8Array(encrypted), iv.length);
|
|
1191
|
+
return this.base64Encode(combined);
|
|
1192
|
+
}
|
|
1193
|
+
async decrypt(encrypted) {
|
|
1194
|
+
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
1195
|
+
const decoder2 = new TextDecoder();
|
|
1196
|
+
return decoder2.decode(this.base64Decode(encrypted));
|
|
1197
|
+
}
|
|
1198
|
+
const bytes = this.base64Decode(encrypted);
|
|
1199
|
+
const iv = bytes.slice(0, 12);
|
|
1200
|
+
const data = bytes.slice(12);
|
|
1201
|
+
const encoder = new TextEncoder();
|
|
1202
|
+
const decoder = new TextDecoder();
|
|
1203
|
+
const passphrase = await this.getWebEncryptionKey();
|
|
1204
|
+
const keyMaterial = await window.crypto.subtle.importKey(
|
|
1205
|
+
"raw",
|
|
1206
|
+
encoder.encode(passphrase),
|
|
1207
|
+
"PBKDF2",
|
|
1208
|
+
false,
|
|
1209
|
+
["deriveBits", "deriveKey"]
|
|
1210
|
+
);
|
|
1211
|
+
const key = await window.crypto.subtle.deriveKey(
|
|
1212
|
+
{
|
|
1213
|
+
name: "PBKDF2",
|
|
1214
|
+
salt: encoder.encode("lanonasis-token-salt"),
|
|
1215
|
+
iterations: 1e5,
|
|
1216
|
+
hash: "SHA-256"
|
|
1217
|
+
},
|
|
1218
|
+
keyMaterial,
|
|
1219
|
+
{ name: "AES-GCM", length: 256 },
|
|
1220
|
+
true,
|
|
1221
|
+
["encrypt", "decrypt"]
|
|
1222
|
+
);
|
|
1223
|
+
const decrypted = await window.crypto.subtle.decrypt(
|
|
1224
|
+
{ name: "AES-GCM", iv },
|
|
1225
|
+
key,
|
|
1226
|
+
data
|
|
1227
|
+
);
|
|
1228
|
+
return decoder.decode(decrypted);
|
|
1229
|
+
}
|
|
1230
|
+
async getWebEncryptionKey() {
|
|
1231
|
+
const existing = localStorage.getItem(this.webEncryptionKeyStorage);
|
|
1232
|
+
if (existing) return existing;
|
|
1233
|
+
const buf = new Uint8Array(32);
|
|
1234
|
+
if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
|
|
1235
|
+
window.crypto.getRandomValues(buf);
|
|
1236
|
+
}
|
|
1237
|
+
const raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1238
|
+
localStorage.setItem(this.webEncryptionKeyStorage, raw);
|
|
1239
|
+
return raw;
|
|
1240
|
+
}
|
|
1241
|
+
base64Encode(bytes) {
|
|
1242
|
+
let binary = "";
|
|
1243
|
+
bytes.forEach((b) => {
|
|
1244
|
+
binary += String.fromCharCode(b);
|
|
1245
|
+
});
|
|
1246
|
+
return btoa(binary);
|
|
1247
|
+
}
|
|
1248
|
+
base64Decode(value) {
|
|
1249
|
+
const binary = atob(value);
|
|
1250
|
+
const bytes = new Uint8Array(binary.length);
|
|
1251
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1252
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1253
|
+
}
|
|
1254
|
+
return bytes;
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1258
|
+
// src/storage/api-key-storage-web.ts
|
|
1259
|
+
var ApiKeyStorageWeb = class {
|
|
1260
|
+
constructor() {
|
|
1261
|
+
this.storageKey = "lanonasis_api_key";
|
|
1262
|
+
this.webEncryptionKeyStorage = "lanonasis_web_enc_key";
|
|
1263
|
+
}
|
|
1264
|
+
async store(data) {
|
|
1265
|
+
const payload = JSON.stringify({
|
|
1266
|
+
...data,
|
|
1267
|
+
createdAt: data.createdAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
1268
|
+
});
|
|
1269
|
+
const encrypted = await this.encrypt(payload);
|
|
1270
|
+
localStorage.setItem(this.storageKey, encrypted);
|
|
1271
|
+
}
|
|
1272
|
+
async retrieve() {
|
|
1273
|
+
const encrypted = localStorage.getItem(this.storageKey);
|
|
1274
|
+
if (!encrypted) return null;
|
|
1275
|
+
try {
|
|
1276
|
+
const decrypted = await this.decrypt(encrypted);
|
|
1277
|
+
return JSON.parse(decrypted);
|
|
1278
|
+
} catch {
|
|
1279
|
+
return null;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
async clear() {
|
|
1283
|
+
localStorage.removeItem(this.storageKey);
|
|
1284
|
+
}
|
|
1285
|
+
async encrypt(text) {
|
|
1286
|
+
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
1287
|
+
const encoder2 = new TextEncoder();
|
|
1288
|
+
return this.base64Encode(encoder2.encode(text));
|
|
1289
|
+
}
|
|
1290
|
+
const encoder = new TextEncoder();
|
|
1291
|
+
const data = encoder.encode(text);
|
|
1292
|
+
const passphrase = await this.getWebEncryptionKey();
|
|
1293
|
+
const keyMaterial = await window.crypto.subtle.importKey(
|
|
1294
|
+
"raw",
|
|
1295
|
+
encoder.encode(passphrase),
|
|
1296
|
+
"PBKDF2",
|
|
1297
|
+
false,
|
|
1298
|
+
["deriveBits", "deriveKey"]
|
|
1299
|
+
);
|
|
1300
|
+
const key = await window.crypto.subtle.deriveKey(
|
|
1301
|
+
{
|
|
1302
|
+
name: "PBKDF2",
|
|
1303
|
+
salt: encoder.encode("lanonasis-api-key-salt"),
|
|
1304
|
+
iterations: 1e5,
|
|
1305
|
+
hash: "SHA-256"
|
|
1306
|
+
},
|
|
1307
|
+
keyMaterial,
|
|
1308
|
+
{ name: "AES-GCM", length: 256 },
|
|
1309
|
+
true,
|
|
1310
|
+
["encrypt", "decrypt"]
|
|
1311
|
+
);
|
|
1312
|
+
const iv = window.crypto.getRandomValues(new Uint8Array(12));
|
|
1313
|
+
const encrypted = await window.crypto.subtle.encrypt(
|
|
1314
|
+
{ name: "AES-GCM", iv },
|
|
1315
|
+
key,
|
|
1316
|
+
data
|
|
1317
|
+
);
|
|
1318
|
+
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
1319
|
+
combined.set(iv, 0);
|
|
1320
|
+
combined.set(new Uint8Array(encrypted), iv.length);
|
|
1321
|
+
return this.base64Encode(combined);
|
|
1322
|
+
}
|
|
1323
|
+
async decrypt(encrypted) {
|
|
1324
|
+
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
1325
|
+
const decoder2 = new TextDecoder();
|
|
1326
|
+
return decoder2.decode(this.base64Decode(encrypted));
|
|
1327
|
+
}
|
|
1328
|
+
const bytes = this.base64Decode(encrypted);
|
|
1329
|
+
const iv = bytes.slice(0, 12);
|
|
1330
|
+
const data = bytes.slice(12);
|
|
1331
|
+
const encoder = new TextEncoder();
|
|
1332
|
+
const decoder = new TextDecoder();
|
|
1333
|
+
const passphrase = await this.getWebEncryptionKey();
|
|
1334
|
+
const keyMaterial = await window.crypto.subtle.importKey(
|
|
1335
|
+
"raw",
|
|
1336
|
+
encoder.encode(passphrase),
|
|
1337
|
+
"PBKDF2",
|
|
1338
|
+
false,
|
|
1339
|
+
["deriveBits", "deriveKey"]
|
|
1340
|
+
);
|
|
1341
|
+
const key = await window.crypto.subtle.deriveKey(
|
|
1342
|
+
{
|
|
1343
|
+
name: "PBKDF2",
|
|
1344
|
+
salt: encoder.encode("lanonasis-api-key-salt"),
|
|
1345
|
+
iterations: 1e5,
|
|
1346
|
+
hash: "SHA-256"
|
|
1347
|
+
},
|
|
1348
|
+
keyMaterial,
|
|
1349
|
+
{ name: "AES-GCM", length: 256 },
|
|
1350
|
+
true,
|
|
1351
|
+
["encrypt", "decrypt"]
|
|
1352
|
+
);
|
|
1353
|
+
const decrypted = await window.crypto.subtle.decrypt(
|
|
1354
|
+
{ name: "AES-GCM", iv },
|
|
1355
|
+
key,
|
|
1356
|
+
data
|
|
1357
|
+
);
|
|
1358
|
+
return decoder.decode(decrypted);
|
|
1359
|
+
}
|
|
1360
|
+
async getWebEncryptionKey() {
|
|
1361
|
+
const existing = localStorage.getItem(this.webEncryptionKeyStorage);
|
|
1362
|
+
if (existing) return existing;
|
|
1363
|
+
const buf = new Uint8Array(32);
|
|
1364
|
+
if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
|
|
1365
|
+
window.crypto.getRandomValues(buf);
|
|
1366
|
+
}
|
|
1367
|
+
const raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1368
|
+
localStorage.setItem(this.webEncryptionKeyStorage, raw);
|
|
1369
|
+
return raw;
|
|
1370
|
+
}
|
|
1371
|
+
base64Encode(bytes) {
|
|
1372
|
+
let binary = "";
|
|
1373
|
+
bytes.forEach((b) => {
|
|
1374
|
+
binary += String.fromCharCode(b);
|
|
1375
|
+
});
|
|
1376
|
+
return btoa(binary);
|
|
1377
|
+
}
|
|
1378
|
+
base64Decode(value) {
|
|
1379
|
+
const binary = atob(value);
|
|
1380
|
+
const bytes = new Uint8Array(binary.length);
|
|
1381
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1382
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1383
|
+
}
|
|
1384
|
+
return bytes;
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
// src/client/mcp-client.ts
|
|
1389
|
+
var import_cross_fetch4 = __toESM(require("cross-fetch"), 1);
|
|
1390
|
+
|
|
1077
1391
|
// src/flows/apikey-flow.ts
|
|
1392
|
+
var import_cross_fetch3 = __toESM(require("cross-fetch"), 1);
|
|
1078
1393
|
var APIKeyFlow = class extends BaseOAuthFlow {
|
|
1079
1394
|
constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
|
|
1080
1395
|
super({
|
|
@@ -1117,7 +1432,7 @@ var APIKeyFlow = class extends BaseOAuthFlow {
|
|
|
1117
1432
|
*/
|
|
1118
1433
|
async validateAPIKey() {
|
|
1119
1434
|
try {
|
|
1120
|
-
const response = await
|
|
1435
|
+
const response = await (0, import_cross_fetch3.default)(`${this.config.authBaseUrl}/api/v1/health`, {
|
|
1121
1436
|
headers: {
|
|
1122
1437
|
"x-api-key": this.apiKey
|
|
1123
1438
|
}
|
|
@@ -1143,7 +1458,8 @@ var MCPClient = class {
|
|
|
1143
1458
|
autoRefresh: true,
|
|
1144
1459
|
...config
|
|
1145
1460
|
};
|
|
1146
|
-
|
|
1461
|
+
const defaultStorage = typeof window !== "undefined" ? new TokenStorageWeb() : new TokenStorage();
|
|
1462
|
+
this.tokenStorage = config.tokenStorage || defaultStorage;
|
|
1147
1463
|
this.authMode = config.apiKey ? "apikey" : "oauth";
|
|
1148
1464
|
if (this.authMode === "apikey") {
|
|
1149
1465
|
this.authFlow = new APIKeyFlow(
|
|
@@ -1358,7 +1674,7 @@ var MCPClient = class {
|
|
|
1358
1674
|
} else {
|
|
1359
1675
|
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
1360
1676
|
}
|
|
1361
|
-
const response = await
|
|
1677
|
+
const response = await (0, import_cross_fetch4.default)(`${this.config.mcpEndpoint}/api`, {
|
|
1362
1678
|
method: "POST",
|
|
1363
1679
|
headers,
|
|
1364
1680
|
body: JSON.stringify({
|
|
@@ -1457,12 +1773,3 @@ var MCPClient = class {
|
|
|
1457
1773
|
return this.request("memory/delete", { id });
|
|
1458
1774
|
}
|
|
1459
1775
|
};
|
|
1460
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
1461
|
-
0 && (module.exports = {
|
|
1462
|
-
ApiKeyStorage,
|
|
1463
|
-
BaseOAuthFlow,
|
|
1464
|
-
DesktopOAuthFlow,
|
|
1465
|
-
MCPClient,
|
|
1466
|
-
TerminalOAuthFlow,
|
|
1467
|
-
TokenStorage
|
|
1468
|
-
});
|