@lanonasis/oauth-client 1.2.1 → 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/dist/index.mjs CHANGED
@@ -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 || "mcp:read mcp:write api_keys:manage";
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 window !== "undefined" && window.crypto) {
30
- window.crypto.getRandomValues(array);
33
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
34
+ crypto.getRandomValues(array);
31
35
  } else {
32
- const crypto = __require("crypto");
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
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
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 fetch(`${this.authBaseUrl}/oauth/device`, {
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 fetch(`${this.authBaseUrl}/oauth/token`, {
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 window !== "undefined" && window.crypto) {
207
- window.crypto.getRandomValues(array);
210
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
211
+ crypto.getRandomValues(array);
208
212
  } else {
209
- const crypto = __require("crypto");
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
- if (typeof window !== "undefined" && window.crypto?.subtle) {
216
- const encoder = new TextEncoder();
217
- const data = encoder.encode(verifier);
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({
@@ -406,6 +407,9 @@ var TokenStorage = class {
406
407
  }
407
408
  }
408
409
  isTokenExpired(tokens) {
410
+ if (tokens.token_type === "api-key" || tokens.expires_in === 0) {
411
+ return false;
412
+ }
409
413
  if (!tokens.expires_in) return false;
410
414
  if (!tokens.issued_at) {
411
415
  console.warn("Token missing issued_at timestamp, treating as expired");
@@ -420,13 +424,13 @@ var TokenStorage = class {
420
424
  const fs = __require("fs").promises;
421
425
  const path = __require("path");
422
426
  const os = __require("os");
423
- const crypto = __require("crypto");
427
+ const crypto2 = __require("crypto");
424
428
  const configDir = path.join(os.homedir(), ".lanonasis");
425
429
  const tokenFile = path.join(configDir, "mcp-tokens.enc");
426
430
  await fs.mkdir(configDir, { recursive: true });
427
431
  const key = this.getFileEncryptionKey();
428
- const iv = crypto.randomBytes(16);
429
- const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
432
+ const iv = crypto2.randomBytes(16);
433
+ const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
430
434
  let encrypted = cipher.update(tokenString, "utf8", "hex");
431
435
  encrypted += cipher.final("hex");
432
436
  const authTag = cipher.getAuthTag().toString("hex");
@@ -438,7 +442,7 @@ var TokenStorage = class {
438
442
  const fs = __require("fs").promises;
439
443
  const path = __require("path");
440
444
  const os = __require("os");
441
- const crypto = __require("crypto");
445
+ const crypto2 = __require("crypto");
442
446
  const tokenFile = path.join(os.homedir(), ".lanonasis", "mcp-tokens.enc");
443
447
  try {
444
448
  const data = await fs.readFile(tokenFile, "utf8");
@@ -448,7 +452,7 @@ var TokenStorage = class {
448
452
  const [ivHex, authTagHex, encrypted] = parts;
449
453
  const iv = Buffer.from(ivHex, "hex");
450
454
  const authTag = Buffer.from(authTagHex, "hex");
451
- const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
455
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
452
456
  decipher.setAuthTag(authTag);
453
457
  let decrypted = decipher.update(encrypted, "hex", "utf8");
454
458
  decrypted += decipher.final("utf8");
@@ -457,7 +461,7 @@ var TokenStorage = class {
457
461
  if (parts.length === 2) {
458
462
  const [ivHex, encrypted] = parts;
459
463
  const iv = Buffer.from(ivHex, "hex");
460
- const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
464
+ const decipher = crypto2.createDecipheriv("aes-256-cbc", key, iv);
461
465
  let decrypted = decipher.update(encrypted, "hex", "utf8");
462
466
  decrypted += decipher.final("utf8");
463
467
  return decrypted;
@@ -479,17 +483,17 @@ var TokenStorage = class {
479
483
  }
480
484
  }
481
485
  getFileEncryptionKey() {
482
- const crypto = __require("crypto");
486
+ const crypto2 = __require("crypto");
483
487
  const os = __require("os");
484
488
  const machineId = os.hostname() + os.userInfo().username;
485
489
  const salt = "lanonasis-mcp-oauth-2024";
486
- return crypto.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
490
+ return crypto2.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
487
491
  }
488
492
  async encrypt(text) {
489
493
  if (typeof window === "undefined" || !window.crypto?.subtle) {
490
494
  const encoder2 = new TextEncoder();
491
495
  const data2 = encoder2.encode(text);
492
- return btoa(String.fromCharCode(...data2));
496
+ return this.base64Encode(data2);
493
497
  }
494
498
  const encoder = new TextEncoder();
495
499
  const data = encoder.encode(text);
@@ -522,23 +526,15 @@ var TokenStorage = class {
522
526
  const combined = new Uint8Array(iv.length + encrypted.byteLength);
523
527
  combined.set(iv, 0);
524
528
  combined.set(new Uint8Array(encrypted), iv.length);
525
- return btoa(String.fromCharCode(...combined));
529
+ return this.base64Encode(combined);
526
530
  }
527
531
  async decrypt(encrypted) {
528
532
  if (typeof window === "undefined" || !window.crypto?.subtle) {
529
- const binary2 = atob(encrypted);
530
- const bytes2 = new Uint8Array(binary2.length);
531
- for (let i = 0; i < binary2.length; i++) {
532
- bytes2[i] = binary2.charCodeAt(i);
533
- }
533
+ const bytes2 = this.base64Decode(encrypted);
534
534
  const decoder2 = new TextDecoder();
535
535
  return decoder2.decode(bytes2);
536
536
  }
537
- const binary = atob(encrypted);
538
- const bytes = new Uint8Array(binary.length);
539
- for (let i = 0; i < binary.length; i++) {
540
- bytes[i] = binary.charCodeAt(i);
541
- }
537
+ const bytes = this.base64Decode(encrypted);
542
538
  const iv = bytes.slice(0, 12);
543
539
  const data = bytes.slice(12);
544
540
  const encoder = new TextEncoder();
@@ -579,6 +575,33 @@ var TokenStorage = class {
579
575
  isMobile() {
580
576
  return typeof window !== "undefined" && window.SecureStorage !== void 0;
581
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
+ }
582
605
  async getWebEncryptionKey() {
583
606
  const existing = typeof localStorage !== "undefined" ? localStorage.getItem(this.webEncryptionKeyStorage) : null;
584
607
  if (existing) {
@@ -590,7 +613,8 @@ var TokenStorage = class {
590
613
  window.crypto.getRandomValues(buf);
591
614
  raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
592
615
  } else {
593
- raw = `${navigator.userAgent}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
616
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "node";
617
+ raw = `${ua}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
594
618
  }
595
619
  if (typeof localStorage !== "undefined") {
596
620
  localStorage.setItem(this.webEncryptionKeyStorage, raw);
@@ -802,13 +826,13 @@ var ApiKeyStorage = class {
802
826
  const fs = __require("fs").promises;
803
827
  const path = __require("path");
804
828
  const os = __require("os");
805
- const crypto = __require("crypto");
829
+ const crypto2 = __require("crypto");
806
830
  const configDir = path.join(os.homedir(), ".lanonasis");
807
831
  const keyFile = path.join(configDir, "api-key.enc");
808
832
  await fs.mkdir(configDir, { recursive: true, mode: 448 });
809
833
  const key = this.getFileEncryptionKey();
810
- const iv = crypto.randomBytes(16);
811
- const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
834
+ const iv = crypto2.randomBytes(16);
835
+ const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
812
836
  let encrypted = cipher.update(keyString, "utf8", "hex");
813
837
  encrypted += cipher.final("hex");
814
838
  const authTag = cipher.getAuthTag();
@@ -820,7 +844,7 @@ var ApiKeyStorage = class {
820
844
  const fs = __require("fs").promises;
821
845
  const path = __require("path");
822
846
  const os = __require("os");
823
- const crypto = __require("crypto");
847
+ const crypto2 = __require("crypto");
824
848
  const keyFile = path.join(os.homedir(), ".lanonasis", "api-key.enc");
825
849
  try {
826
850
  const data = await fs.readFile(keyFile, "utf8");
@@ -831,7 +855,7 @@ var ApiKeyStorage = class {
831
855
  const key = this.getFileEncryptionKey();
832
856
  const iv = Buffer.from(ivHex, "hex");
833
857
  const authTag = Buffer.from(authTagHex, "hex");
834
- const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
858
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
835
859
  decipher.setAuthTag(authTag);
836
860
  let decrypted = decipher.update(encrypted, "hex", "utf8");
837
861
  decrypted += decipher.final("utf8");
@@ -875,18 +899,18 @@ var ApiKeyStorage = class {
875
899
  }
876
900
  }
877
901
  getFileEncryptionKey() {
878
- const crypto = __require("crypto");
902
+ const crypto2 = __require("crypto");
879
903
  const os = __require("os");
880
904
  const machineId = os.hostname() + os.userInfo().username;
881
905
  const salt = "lanonasis-mcp-api-key-2024";
882
- return crypto.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
906
+ return crypto2.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
883
907
  }
884
908
  // ==================== Web Encryption ====================
885
909
  async encrypt(text) {
886
910
  if (typeof window === "undefined" || !window.crypto || !window.crypto.subtle) {
887
911
  const encoder = new TextEncoder();
888
912
  const data = encoder.encode(text);
889
- return btoa(String.fromCharCode(...data));
913
+ return this.base64Encode(data);
890
914
  }
891
915
  try {
892
916
  const encoder = new TextEncoder();
@@ -925,16 +949,12 @@ var ApiKeyStorage = class {
925
949
  console.error("Web encryption failed:", error);
926
950
  const encoder = new TextEncoder();
927
951
  const data = encoder.encode(text);
928
- return btoa(String.fromCharCode(...data));
952
+ return this.base64Encode(data);
929
953
  }
930
954
  }
931
955
  async decrypt(encrypted) {
932
956
  if (typeof window === "undefined" || !window.crypto || !window.crypto.subtle) {
933
- const binary = atob(encrypted);
934
- const bytes = new Uint8Array(binary.length);
935
- for (let i = 0; i < binary.length; i++) {
936
- bytes[i] = binary.charCodeAt(i);
937
- }
957
+ const bytes = this.base64Decode(encrypted);
938
958
  const decoder = new TextDecoder();
939
959
  return decoder.decode(bytes);
940
960
  }
@@ -976,11 +996,7 @@ var ApiKeyStorage = class {
976
996
  return decoder.decode(decrypted);
977
997
  } catch (error) {
978
998
  console.error("Web decryption failed:", error);
979
- const binary = atob(encrypted);
980
- const bytes = new Uint8Array(binary.length);
981
- for (let i = 0; i < binary.length; i++) {
982
- bytes[i] = binary.charCodeAt(i);
983
- }
999
+ const bytes = this.base64Decode(encrypted);
984
1000
  const decoder = new TextDecoder();
985
1001
  return decoder.decode(bytes);
986
1002
  }
@@ -996,7 +1012,8 @@ var ApiKeyStorage = class {
996
1012
  window.crypto.getRandomValues(buf);
997
1013
  raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
998
1014
  } else {
999
- raw = `${navigator.userAgent}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
1015
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "node";
1016
+ raw = `${ua}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
1000
1017
  }
1001
1018
  if (typeof localStorage !== "undefined") {
1002
1019
  localStorage.setItem(this.webEncryptionKeyStorage, raw);
@@ -1013,6 +1030,33 @@ var ApiKeyStorage = class {
1013
1030
  isMobile() {
1014
1031
  return typeof window !== "undefined" && window.SecureStorage !== void 0;
1015
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
+ }
1016
1060
  /**
1017
1061
  * Normalize API keys to a SHA-256 hex digest.
1018
1062
  * Accepts pre-hashed input and lowercases it to prevent double hashing.
@@ -1037,9 +1081,338 @@ var ApiKeyStorage = class {
1037
1081
  }
1038
1082
  };
1039
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
+
1355
+ // src/flows/apikey-flow.ts
1356
+ import fetch3 from "cross-fetch";
1357
+ var APIKeyFlow = class extends BaseOAuthFlow {
1358
+ constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
1359
+ super({
1360
+ clientId: "api-key-client",
1361
+ authBaseUrl
1362
+ });
1363
+ this.apiKey = apiKey;
1364
+ }
1365
+ /**
1366
+ * "Authenticate" by returning the API key as a virtual token
1367
+ * The API key will be used directly in request headers
1368
+ */
1369
+ async authenticate() {
1370
+ if (!this.apiKey || !this.apiKey.startsWith("lano_") && !this.apiKey.startsWith("vx_")) {
1371
+ throw new Error(
1372
+ 'Invalid API key format. Must start with "lano_" or "vx_". Please regenerate your API key from the dashboard.'
1373
+ );
1374
+ }
1375
+ if (this.apiKey.startsWith("vx_")) {
1376
+ console.warn(
1377
+ '\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.'
1378
+ );
1379
+ }
1380
+ return {
1381
+ access_token: this.apiKey,
1382
+ token_type: "api-key",
1383
+ expires_in: 0,
1384
+ // API keys don't expire
1385
+ issued_at: Date.now()
1386
+ };
1387
+ }
1388
+ /**
1389
+ * API keys don't need refresh
1390
+ */
1391
+ async refreshToken(refreshToken) {
1392
+ throw new Error("API keys do not support token refresh");
1393
+ }
1394
+ /**
1395
+ * Optional: Validate API key by making a test request
1396
+ */
1397
+ async validateAPIKey() {
1398
+ try {
1399
+ const response = await fetch3(`${this.config.authBaseUrl}/api/v1/health`, {
1400
+ headers: {
1401
+ "x-api-key": this.apiKey
1402
+ }
1403
+ });
1404
+ return response.ok;
1405
+ } catch (error) {
1406
+ console.error("API key validation failed:", error);
1407
+ return false;
1408
+ }
1409
+ }
1410
+ };
1411
+
1040
1412
  // src/client/mcp-client.ts
1041
1413
  var MCPClient = class {
1042
1414
  constructor(config = {}) {
1415
+ // ← NEW: Track auth mode
1043
1416
  this.ws = null;
1044
1417
  this.eventSource = null;
1045
1418
  this.accessToken = null;
@@ -1049,31 +1422,47 @@ var MCPClient = class {
1049
1422
  autoRefresh: true,
1050
1423
  ...config
1051
1424
  };
1052
- this.tokenStorage = new TokenStorage();
1053
- if (this.isTerminal()) {
1054
- this.authFlow = new TerminalOAuthFlow(config);
1425
+ const defaultStorage = typeof window !== "undefined" ? new TokenStorageWeb() : new TokenStorage();
1426
+ this.tokenStorage = config.tokenStorage || defaultStorage;
1427
+ this.authMode = config.apiKey ? "apikey" : "oauth";
1428
+ if (this.authMode === "apikey") {
1429
+ this.authFlow = new APIKeyFlow(
1430
+ config.apiKey,
1431
+ config.authBaseUrl || "https://mcp.lanonasis.com"
1432
+ );
1055
1433
  } else {
1056
- this.authFlow = new DesktopOAuthFlow(config);
1434
+ if (this.isTerminal()) {
1435
+ this.authFlow = new TerminalOAuthFlow(config);
1436
+ } else {
1437
+ this.authFlow = new DesktopOAuthFlow(config);
1438
+ }
1057
1439
  }
1058
1440
  }
1059
1441
  async connect() {
1060
1442
  try {
1061
1443
  let tokens = await this.tokenStorage.retrieve();
1062
- if (!tokens || this.tokenStorage.isTokenExpired(tokens)) {
1063
- if (tokens?.refresh_token) {
1064
- try {
1065
- tokens = await this.authFlow.refreshToken(tokens.refresh_token);
1066
- await this.tokenStorage.store(tokens);
1067
- } catch (error) {
1444
+ if (this.authMode === "apikey") {
1445
+ if (!tokens) {
1446
+ tokens = await this.authenticate();
1447
+ }
1448
+ this.accessToken = tokens.access_token;
1449
+ } else {
1450
+ if (!tokens || this.tokenStorage.isTokenExpired(tokens)) {
1451
+ if (tokens?.refresh_token) {
1452
+ try {
1453
+ tokens = await this.authFlow.refreshToken(tokens.refresh_token);
1454
+ await this.tokenStorage.store(tokens);
1455
+ } catch (error) {
1456
+ tokens = await this.authenticate();
1457
+ }
1458
+ } else {
1068
1459
  tokens = await this.authenticate();
1069
1460
  }
1070
- } else {
1071
- tokens = await this.authenticate();
1072
1461
  }
1073
- }
1074
- this.accessToken = tokens.access_token;
1075
- if (this.config.autoRefresh && tokens.expires_in) {
1076
- this.scheduleTokenRefresh(tokens);
1462
+ this.accessToken = tokens.access_token;
1463
+ if (this.config.autoRefresh && tokens.expires_in) {
1464
+ this.scheduleTokenRefresh(tokens);
1465
+ }
1077
1466
  }
1078
1467
  await this.establishConnection();
1079
1468
  } catch (error) {
@@ -1093,6 +1482,10 @@ var MCPClient = class {
1093
1482
  if (!tokens) {
1094
1483
  throw new Error("Not authenticated");
1095
1484
  }
1485
+ if (this.authMode === "apikey") {
1486
+ this.accessToken = tokens.access_token;
1487
+ return;
1488
+ }
1096
1489
  if (this.tokenStorage.isTokenExpired(tokens)) {
1097
1490
  if (tokens.refresh_token) {
1098
1491
  try {
@@ -1149,11 +1542,19 @@ var MCPClient = class {
1149
1542
  this.ws = new WebSocket(wsUrl.toString());
1150
1543
  } else {
1151
1544
  const { default: WS } = await import("ws");
1152
- this.ws = new WS(wsUrl.toString(), {
1153
- headers: {
1154
- "Authorization": `Bearer ${this.accessToken}`
1155
- }
1156
- });
1545
+ if (this.authMode === "apikey") {
1546
+ this.ws = new WS(wsUrl.toString(), {
1547
+ headers: {
1548
+ "x-api-key": this.accessToken
1549
+ }
1550
+ });
1551
+ } else {
1552
+ this.ws = new WS(wsUrl.toString(), {
1553
+ headers: {
1554
+ "Authorization": `Bearer ${this.accessToken}`
1555
+ }
1556
+ });
1557
+ }
1157
1558
  }
1158
1559
  return new Promise((resolve, reject) => {
1159
1560
  if (!this.ws) {
@@ -1187,11 +1588,19 @@ var MCPClient = class {
1187
1588
  } else {
1188
1589
  const EventSourceModule = await import("eventsource");
1189
1590
  const ES = EventSourceModule.default || EventSourceModule;
1190
- this.eventSource = new ES(sseUrl.toString(), {
1191
- headers: {
1192
- "Authorization": `Bearer ${this.accessToken}`
1193
- }
1194
- });
1591
+ if (this.authMode === "apikey") {
1592
+ this.eventSource = new ES(sseUrl.toString(), {
1593
+ headers: {
1594
+ "x-api-key": this.accessToken
1595
+ }
1596
+ });
1597
+ } else {
1598
+ this.eventSource = new ES(sseUrl.toString(), {
1599
+ headers: {
1600
+ "Authorization": `Bearer ${this.accessToken}`
1601
+ }
1602
+ });
1603
+ }
1195
1604
  }
1196
1605
  this.eventSource.onopen = () => {
1197
1606
  console.log("MCP SSE connected");
@@ -1221,12 +1630,17 @@ var MCPClient = class {
1221
1630
  if (!this.accessToken) {
1222
1631
  throw new Error("Not authenticated");
1223
1632
  }
1224
- const response = await fetch(`${this.config.mcpEndpoint}/api`, {
1633
+ const headers = {
1634
+ "Content-Type": "application/json"
1635
+ };
1636
+ if (this.authMode === "apikey") {
1637
+ headers["x-api-key"] = this.accessToken;
1638
+ } else {
1639
+ headers["Authorization"] = `Bearer ${this.accessToken}`;
1640
+ }
1641
+ const response = await fetch4(`${this.config.mcpEndpoint}/api`, {
1225
1642
  method: "POST",
1226
- headers: {
1227
- "Authorization": `Bearer ${this.accessToken}`,
1228
- "Content-Type": "application/json"
1229
- },
1643
+ headers,
1230
1644
  body: JSON.stringify({
1231
1645
  jsonrpc: "2.0",
1232
1646
  id: this.generateId(),
@@ -1235,6 +1649,9 @@ var MCPClient = class {
1235
1649
  })
1236
1650
  });
1237
1651
  if (response.status === 401) {
1652
+ if (this.authMode === "apikey") {
1653
+ throw new Error("Invalid API key - please check your credentials");
1654
+ }
1238
1655
  const tokens = await this.tokenStorage.retrieve();
1239
1656
  if (tokens?.refresh_token) {
1240
1657
  const newTokens = await this.authFlow.refreshToken(tokens.refresh_token);
@@ -1322,9 +1739,11 @@ var MCPClient = class {
1322
1739
  };
1323
1740
  export {
1324
1741
  ApiKeyStorage,
1742
+ ApiKeyStorageWeb,
1325
1743
  BaseOAuthFlow,
1326
1744
  DesktopOAuthFlow,
1327
1745
  MCPClient,
1328
1746
  TerminalOAuthFlow,
1329
- TokenStorage
1747
+ TokenStorage,
1748
+ TokenStorageWeb
1330
1749
  };