@lanonasis/oauth-client 1.2.1 → 1.2.3

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/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 || "mcp:read mcp:write api_keys:manage";
53
+ this.scope = config.scope || "memories:read memories:write memories:delete profile";
48
54
  }
49
55
  async makeTokenRequest(body) {
50
- const response = await fetch(`${this.authBaseUrl}/oauth/token`, {
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 window !== "undefined" && window.crypto) {
64
- window.crypto.getRandomValues(array);
69
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
70
+ crypto.getRandomValues(array);
65
71
  } else {
66
- const crypto = require("crypto");
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
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
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 fetch(`${this.authBaseUrl}/oauth/revoke`, {
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 fetch(`${this.authBaseUrl}/oauth/device`, {
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 (0, import_open.default)(url);
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 fetch(`${this.authBaseUrl}/oauth/token`, {
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 window !== "undefined" && window.crypto) {
241
- window.crypto.getRandomValues(array);
246
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
247
+ crypto.getRandomValues(array);
242
248
  } else {
243
- const crypto = require("crypto");
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
- if (typeof window !== "undefined" && window.crypto?.subtle) {
250
- const encoder = new TextEncoder();
251
- const data = encoder.encode(verifier);
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({
@@ -440,6 +443,9 @@ var TokenStorage = class {
440
443
  }
441
444
  }
442
445
  isTokenExpired(tokens) {
446
+ if (tokens.token_type === "api-key" || tokens.expires_in === 0) {
447
+ return false;
448
+ }
443
449
  if (!tokens.expires_in) return false;
444
450
  if (!tokens.issued_at) {
445
451
  console.warn("Token missing issued_at timestamp, treating as expired");
@@ -454,13 +460,13 @@ var TokenStorage = class {
454
460
  const fs = require("fs").promises;
455
461
  const path = require("path");
456
462
  const os = require("os");
457
- const crypto = require("crypto");
463
+ const crypto2 = require("crypto");
458
464
  const configDir = path.join(os.homedir(), ".lanonasis");
459
465
  const tokenFile = path.join(configDir, "mcp-tokens.enc");
460
466
  await fs.mkdir(configDir, { recursive: true });
461
467
  const key = this.getFileEncryptionKey();
462
- const iv = crypto.randomBytes(16);
463
- const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
468
+ const iv = crypto2.randomBytes(16);
469
+ const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
464
470
  let encrypted = cipher.update(tokenString, "utf8", "hex");
465
471
  encrypted += cipher.final("hex");
466
472
  const authTag = cipher.getAuthTag().toString("hex");
@@ -472,7 +478,7 @@ var TokenStorage = class {
472
478
  const fs = require("fs").promises;
473
479
  const path = require("path");
474
480
  const os = require("os");
475
- const crypto = require("crypto");
481
+ const crypto2 = require("crypto");
476
482
  const tokenFile = path.join(os.homedir(), ".lanonasis", "mcp-tokens.enc");
477
483
  try {
478
484
  const data = await fs.readFile(tokenFile, "utf8");
@@ -482,7 +488,7 @@ var TokenStorage = class {
482
488
  const [ivHex, authTagHex, encrypted] = parts;
483
489
  const iv = Buffer.from(ivHex, "hex");
484
490
  const authTag = Buffer.from(authTagHex, "hex");
485
- const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
491
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
486
492
  decipher.setAuthTag(authTag);
487
493
  let decrypted = decipher.update(encrypted, "hex", "utf8");
488
494
  decrypted += decipher.final("utf8");
@@ -491,7 +497,7 @@ var TokenStorage = class {
491
497
  if (parts.length === 2) {
492
498
  const [ivHex, encrypted] = parts;
493
499
  const iv = Buffer.from(ivHex, "hex");
494
- const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
500
+ const decipher = crypto2.createDecipheriv("aes-256-cbc", key, iv);
495
501
  let decrypted = decipher.update(encrypted, "hex", "utf8");
496
502
  decrypted += decipher.final("utf8");
497
503
  return decrypted;
@@ -513,17 +519,17 @@ var TokenStorage = class {
513
519
  }
514
520
  }
515
521
  getFileEncryptionKey() {
516
- const crypto = require("crypto");
522
+ const crypto2 = require("crypto");
517
523
  const os = require("os");
518
524
  const machineId = os.hostname() + os.userInfo().username;
519
525
  const salt = "lanonasis-mcp-oauth-2024";
520
- return crypto.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
526
+ return crypto2.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
521
527
  }
522
528
  async encrypt(text) {
523
529
  if (typeof window === "undefined" || !window.crypto?.subtle) {
524
530
  const encoder2 = new TextEncoder();
525
531
  const data2 = encoder2.encode(text);
526
- return btoa(String.fromCharCode(...data2));
532
+ return this.base64Encode(data2);
527
533
  }
528
534
  const encoder = new TextEncoder();
529
535
  const data = encoder.encode(text);
@@ -556,23 +562,15 @@ var TokenStorage = class {
556
562
  const combined = new Uint8Array(iv.length + encrypted.byteLength);
557
563
  combined.set(iv, 0);
558
564
  combined.set(new Uint8Array(encrypted), iv.length);
559
- return btoa(String.fromCharCode(...combined));
565
+ return this.base64Encode(combined);
560
566
  }
561
567
  async decrypt(encrypted) {
562
568
  if (typeof window === "undefined" || !window.crypto?.subtle) {
563
- const binary2 = atob(encrypted);
564
- const bytes2 = new Uint8Array(binary2.length);
565
- for (let i = 0; i < binary2.length; i++) {
566
- bytes2[i] = binary2.charCodeAt(i);
567
- }
569
+ const bytes2 = this.base64Decode(encrypted);
568
570
  const decoder2 = new TextDecoder();
569
571
  return decoder2.decode(bytes2);
570
572
  }
571
- const binary = atob(encrypted);
572
- const bytes = new Uint8Array(binary.length);
573
- for (let i = 0; i < binary.length; i++) {
574
- bytes[i] = binary.charCodeAt(i);
575
- }
573
+ const bytes = this.base64Decode(encrypted);
576
574
  const iv = bytes.slice(0, 12);
577
575
  const data = bytes.slice(12);
578
576
  const encoder = new TextEncoder();
@@ -613,6 +611,33 @@ var TokenStorage = class {
613
611
  isMobile() {
614
612
  return typeof window !== "undefined" && window.SecureStorage !== void 0;
615
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
+ }
616
641
  async getWebEncryptionKey() {
617
642
  const existing = typeof localStorage !== "undefined" ? localStorage.getItem(this.webEncryptionKeyStorage) : null;
618
643
  if (existing) {
@@ -624,7 +649,8 @@ var TokenStorage = class {
624
649
  window.crypto.getRandomValues(buf);
625
650
  raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
626
651
  } else {
627
- raw = `${navigator.userAgent}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
652
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "node";
653
+ raw = `${ua}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
628
654
  }
629
655
  if (typeof localStorage !== "undefined") {
630
656
  localStorage.setItem(this.webEncryptionKeyStorage, raw);
@@ -836,13 +862,13 @@ var ApiKeyStorage = class {
836
862
  const fs = require("fs").promises;
837
863
  const path = require("path");
838
864
  const os = require("os");
839
- const crypto = require("crypto");
865
+ const crypto2 = require("crypto");
840
866
  const configDir = path.join(os.homedir(), ".lanonasis");
841
867
  const keyFile = path.join(configDir, "api-key.enc");
842
868
  await fs.mkdir(configDir, { recursive: true, mode: 448 });
843
869
  const key = this.getFileEncryptionKey();
844
- const iv = crypto.randomBytes(16);
845
- const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
870
+ const iv = crypto2.randomBytes(16);
871
+ const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
846
872
  let encrypted = cipher.update(keyString, "utf8", "hex");
847
873
  encrypted += cipher.final("hex");
848
874
  const authTag = cipher.getAuthTag();
@@ -854,7 +880,7 @@ var ApiKeyStorage = class {
854
880
  const fs = require("fs").promises;
855
881
  const path = require("path");
856
882
  const os = require("os");
857
- const crypto = require("crypto");
883
+ const crypto2 = require("crypto");
858
884
  const keyFile = path.join(os.homedir(), ".lanonasis", "api-key.enc");
859
885
  try {
860
886
  const data = await fs.readFile(keyFile, "utf8");
@@ -865,7 +891,7 @@ var ApiKeyStorage = class {
865
891
  const key = this.getFileEncryptionKey();
866
892
  const iv = Buffer.from(ivHex, "hex");
867
893
  const authTag = Buffer.from(authTagHex, "hex");
868
- const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
894
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
869
895
  decipher.setAuthTag(authTag);
870
896
  let decrypted = decipher.update(encrypted, "hex", "utf8");
871
897
  decrypted += decipher.final("utf8");
@@ -909,18 +935,18 @@ var ApiKeyStorage = class {
909
935
  }
910
936
  }
911
937
  getFileEncryptionKey() {
912
- const crypto = require("crypto");
938
+ const crypto2 = require("crypto");
913
939
  const os = require("os");
914
940
  const machineId = os.hostname() + os.userInfo().username;
915
941
  const salt = "lanonasis-mcp-api-key-2024";
916
- return crypto.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
942
+ return crypto2.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
917
943
  }
918
944
  // ==================== Web Encryption ====================
919
945
  async encrypt(text) {
920
946
  if (typeof window === "undefined" || !window.crypto || !window.crypto.subtle) {
921
947
  const encoder = new TextEncoder();
922
948
  const data = encoder.encode(text);
923
- return btoa(String.fromCharCode(...data));
949
+ return this.base64Encode(data);
924
950
  }
925
951
  try {
926
952
  const encoder = new TextEncoder();
@@ -959,16 +985,12 @@ var ApiKeyStorage = class {
959
985
  console.error("Web encryption failed:", error);
960
986
  const encoder = new TextEncoder();
961
987
  const data = encoder.encode(text);
962
- return btoa(String.fromCharCode(...data));
988
+ return this.base64Encode(data);
963
989
  }
964
990
  }
965
991
  async decrypt(encrypted) {
966
992
  if (typeof window === "undefined" || !window.crypto || !window.crypto.subtle) {
967
- const binary = atob(encrypted);
968
- const bytes = new Uint8Array(binary.length);
969
- for (let i = 0; i < binary.length; i++) {
970
- bytes[i] = binary.charCodeAt(i);
971
- }
993
+ const bytes = this.base64Decode(encrypted);
972
994
  const decoder = new TextDecoder();
973
995
  return decoder.decode(bytes);
974
996
  }
@@ -1010,11 +1032,7 @@ var ApiKeyStorage = class {
1010
1032
  return decoder.decode(decrypted);
1011
1033
  } catch (error) {
1012
1034
  console.error("Web decryption failed:", error);
1013
- const binary = atob(encrypted);
1014
- const bytes = new Uint8Array(binary.length);
1015
- for (let i = 0; i < binary.length; i++) {
1016
- bytes[i] = binary.charCodeAt(i);
1017
- }
1035
+ const bytes = this.base64Decode(encrypted);
1018
1036
  const decoder = new TextDecoder();
1019
1037
  return decoder.decode(bytes);
1020
1038
  }
@@ -1030,7 +1048,8 @@ var ApiKeyStorage = class {
1030
1048
  window.crypto.getRandomValues(buf);
1031
1049
  raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
1032
1050
  } else {
1033
- raw = `${navigator.userAgent}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
1051
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "node";
1052
+ raw = `${ua}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
1034
1053
  }
1035
1054
  if (typeof localStorage !== "undefined") {
1036
1055
  localStorage.setItem(this.webEncryptionKeyStorage, raw);
@@ -1047,6 +1066,33 @@ var ApiKeyStorage = class {
1047
1066
  isMobile() {
1048
1067
  return typeof window !== "undefined" && window.SecureStorage !== void 0;
1049
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
+ }
1050
1096
  /**
1051
1097
  * Normalize API keys to a SHA-256 hex digest.
1052
1098
  * Accepts pre-hashed input and lowercases it to prevent double hashing.
@@ -1071,9 +1117,338 @@ var ApiKeyStorage = class {
1071
1117
  }
1072
1118
  };
1073
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
+
1391
+ // src/flows/apikey-flow.ts
1392
+ var import_cross_fetch3 = __toESM(require("cross-fetch"), 1);
1393
+ var APIKeyFlow = class extends BaseOAuthFlow {
1394
+ constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
1395
+ super({
1396
+ clientId: "api-key-client",
1397
+ authBaseUrl
1398
+ });
1399
+ this.apiKey = apiKey;
1400
+ }
1401
+ /**
1402
+ * "Authenticate" by returning the API key as a virtual token
1403
+ * The API key will be used directly in request headers
1404
+ */
1405
+ async authenticate() {
1406
+ if (!this.apiKey || !this.apiKey.startsWith("lano_") && !this.apiKey.startsWith("vx_")) {
1407
+ throw new Error(
1408
+ 'Invalid API key format. Must start with "lano_" or "vx_". Please regenerate your API key from the dashboard.'
1409
+ );
1410
+ }
1411
+ if (this.apiKey.startsWith("vx_")) {
1412
+ console.warn(
1413
+ '\u26A0\uFE0F DEPRECATION WARNING: API keys with "vx_" prefix are deprecated and will stop working soon. Please regenerate your API key from the dashboard to get a "lano_" prefixed key. Support for "vx_" keys will be removed in a future version.'
1414
+ );
1415
+ }
1416
+ return {
1417
+ access_token: this.apiKey,
1418
+ token_type: "api-key",
1419
+ expires_in: 0,
1420
+ // API keys don't expire
1421
+ issued_at: Date.now()
1422
+ };
1423
+ }
1424
+ /**
1425
+ * API keys don't need refresh
1426
+ */
1427
+ async refreshToken(refreshToken) {
1428
+ throw new Error("API keys do not support token refresh");
1429
+ }
1430
+ /**
1431
+ * Optional: Validate API key by making a test request
1432
+ */
1433
+ async validateAPIKey() {
1434
+ try {
1435
+ const response = await (0, import_cross_fetch3.default)(`${this.config.authBaseUrl}/api/v1/health`, {
1436
+ headers: {
1437
+ "x-api-key": this.apiKey
1438
+ }
1439
+ });
1440
+ return response.ok;
1441
+ } catch (error) {
1442
+ console.error("API key validation failed:", error);
1443
+ return false;
1444
+ }
1445
+ }
1446
+ };
1447
+
1074
1448
  // src/client/mcp-client.ts
1075
1449
  var MCPClient = class {
1076
1450
  constructor(config = {}) {
1451
+ // ← NEW: Track auth mode
1077
1452
  this.ws = null;
1078
1453
  this.eventSource = null;
1079
1454
  this.accessToken = null;
@@ -1083,31 +1458,47 @@ var MCPClient = class {
1083
1458
  autoRefresh: true,
1084
1459
  ...config
1085
1460
  };
1086
- this.tokenStorage = new TokenStorage();
1087
- if (this.isTerminal()) {
1088
- this.authFlow = new TerminalOAuthFlow(config);
1461
+ const defaultStorage = typeof window !== "undefined" ? new TokenStorageWeb() : new TokenStorage();
1462
+ this.tokenStorage = config.tokenStorage || defaultStorage;
1463
+ this.authMode = config.apiKey ? "apikey" : "oauth";
1464
+ if (this.authMode === "apikey") {
1465
+ this.authFlow = new APIKeyFlow(
1466
+ config.apiKey,
1467
+ config.authBaseUrl || "https://mcp.lanonasis.com"
1468
+ );
1089
1469
  } else {
1090
- this.authFlow = new DesktopOAuthFlow(config);
1470
+ if (this.isTerminal()) {
1471
+ this.authFlow = new TerminalOAuthFlow(config);
1472
+ } else {
1473
+ this.authFlow = new DesktopOAuthFlow(config);
1474
+ }
1091
1475
  }
1092
1476
  }
1093
1477
  async connect() {
1094
1478
  try {
1095
1479
  let tokens = await this.tokenStorage.retrieve();
1096
- if (!tokens || this.tokenStorage.isTokenExpired(tokens)) {
1097
- if (tokens?.refresh_token) {
1098
- try {
1099
- tokens = await this.authFlow.refreshToken(tokens.refresh_token);
1100
- await this.tokenStorage.store(tokens);
1101
- } catch (error) {
1480
+ if (this.authMode === "apikey") {
1481
+ if (!tokens) {
1482
+ tokens = await this.authenticate();
1483
+ }
1484
+ this.accessToken = tokens.access_token;
1485
+ } else {
1486
+ if (!tokens || this.tokenStorage.isTokenExpired(tokens)) {
1487
+ if (tokens?.refresh_token) {
1488
+ try {
1489
+ tokens = await this.authFlow.refreshToken(tokens.refresh_token);
1490
+ await this.tokenStorage.store(tokens);
1491
+ } catch (error) {
1492
+ tokens = await this.authenticate();
1493
+ }
1494
+ } else {
1102
1495
  tokens = await this.authenticate();
1103
1496
  }
1104
- } else {
1105
- tokens = await this.authenticate();
1106
1497
  }
1107
- }
1108
- this.accessToken = tokens.access_token;
1109
- if (this.config.autoRefresh && tokens.expires_in) {
1110
- this.scheduleTokenRefresh(tokens);
1498
+ this.accessToken = tokens.access_token;
1499
+ if (this.config.autoRefresh && tokens.expires_in) {
1500
+ this.scheduleTokenRefresh(tokens);
1501
+ }
1111
1502
  }
1112
1503
  await this.establishConnection();
1113
1504
  } catch (error) {
@@ -1127,6 +1518,10 @@ var MCPClient = class {
1127
1518
  if (!tokens) {
1128
1519
  throw new Error("Not authenticated");
1129
1520
  }
1521
+ if (this.authMode === "apikey") {
1522
+ this.accessToken = tokens.access_token;
1523
+ return;
1524
+ }
1130
1525
  if (this.tokenStorage.isTokenExpired(tokens)) {
1131
1526
  if (tokens.refresh_token) {
1132
1527
  try {
@@ -1183,11 +1578,19 @@ var MCPClient = class {
1183
1578
  this.ws = new WebSocket(wsUrl.toString());
1184
1579
  } else {
1185
1580
  const { default: WS } = await import("ws");
1186
- this.ws = new WS(wsUrl.toString(), {
1187
- headers: {
1188
- "Authorization": `Bearer ${this.accessToken}`
1189
- }
1190
- });
1581
+ if (this.authMode === "apikey") {
1582
+ this.ws = new WS(wsUrl.toString(), {
1583
+ headers: {
1584
+ "x-api-key": this.accessToken
1585
+ }
1586
+ });
1587
+ } else {
1588
+ this.ws = new WS(wsUrl.toString(), {
1589
+ headers: {
1590
+ "Authorization": `Bearer ${this.accessToken}`
1591
+ }
1592
+ });
1593
+ }
1191
1594
  }
1192
1595
  return new Promise((resolve, reject) => {
1193
1596
  if (!this.ws) {
@@ -1221,11 +1624,19 @@ var MCPClient = class {
1221
1624
  } else {
1222
1625
  const EventSourceModule = await import("eventsource");
1223
1626
  const ES = EventSourceModule.default || EventSourceModule;
1224
- this.eventSource = new ES(sseUrl.toString(), {
1225
- headers: {
1226
- "Authorization": `Bearer ${this.accessToken}`
1227
- }
1228
- });
1627
+ if (this.authMode === "apikey") {
1628
+ this.eventSource = new ES(sseUrl.toString(), {
1629
+ headers: {
1630
+ "x-api-key": this.accessToken
1631
+ }
1632
+ });
1633
+ } else {
1634
+ this.eventSource = new ES(sseUrl.toString(), {
1635
+ headers: {
1636
+ "Authorization": `Bearer ${this.accessToken}`
1637
+ }
1638
+ });
1639
+ }
1229
1640
  }
1230
1641
  this.eventSource.onopen = () => {
1231
1642
  console.log("MCP SSE connected");
@@ -1255,12 +1666,17 @@ var MCPClient = class {
1255
1666
  if (!this.accessToken) {
1256
1667
  throw new Error("Not authenticated");
1257
1668
  }
1258
- const response = await fetch(`${this.config.mcpEndpoint}/api`, {
1669
+ const headers = {
1670
+ "Content-Type": "application/json"
1671
+ };
1672
+ if (this.authMode === "apikey") {
1673
+ headers["x-api-key"] = this.accessToken;
1674
+ } else {
1675
+ headers["Authorization"] = `Bearer ${this.accessToken}`;
1676
+ }
1677
+ const response = await (0, import_cross_fetch4.default)(`${this.config.mcpEndpoint}/api`, {
1259
1678
  method: "POST",
1260
- headers: {
1261
- "Authorization": `Bearer ${this.accessToken}`,
1262
- "Content-Type": "application/json"
1263
- },
1679
+ headers,
1264
1680
  body: JSON.stringify({
1265
1681
  jsonrpc: "2.0",
1266
1682
  id: this.generateId(),
@@ -1269,6 +1685,9 @@ var MCPClient = class {
1269
1685
  })
1270
1686
  });
1271
1687
  if (response.status === 401) {
1688
+ if (this.authMode === "apikey") {
1689
+ throw new Error("Invalid API key - please check your credentials");
1690
+ }
1272
1691
  const tokens = await this.tokenStorage.retrieve();
1273
1692
  if (tokens?.refresh_token) {
1274
1693
  const newTokens = await this.authFlow.refreshToken(tokens.refresh_token);
@@ -1354,12 +1773,3 @@ var MCPClient = class {
1354
1773
  return this.request("memory/delete", { id });
1355
1774
  }
1356
1775
  };
1357
- // Annotate the CommonJS export names for ESM import in node:
1358
- 0 && (module.exports = {
1359
- ApiKeyStorage,
1360
- BaseOAuthFlow,
1361
- DesktopOAuthFlow,
1362
- MCPClient,
1363
- TerminalOAuthFlow,
1364
- TokenStorage
1365
- });