@lanonasis/oauth-client 1.2.8 → 2.0.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
@@ -398,11 +398,15 @@ var MagicLinkFlow = class extends BaseOAuthFlow {
398
398
  * @returns Response with success status and expiration time
399
399
  */
400
400
  async requestOTP(email) {
401
+ if (typeof email !== "string" || !email.trim()) {
402
+ throw new Error("Invalid email: a non-empty email address is required to request an OTP.");
403
+ }
404
+ const normalizedEmail = email.trim().toLowerCase();
401
405
  const response = await (0, import_cross_fetch3.default)(`${this.authBaseUrl}/v1/auth/otp/send`, {
402
406
  method: "POST",
403
407
  headers: { "Content-Type": "application/json" },
404
408
  body: JSON.stringify({
405
- email: email.trim().toLowerCase(),
409
+ email: normalizedEmail,
406
410
  type: "email",
407
411
  // Explicitly request 6-digit code, not magic link
408
412
  platform: this.platform,
@@ -556,7 +560,19 @@ var MagicLinkFlow = class extends BaseOAuthFlow {
556
560
  return emailRegex.test(email.trim());
557
561
  }
558
562
  /**
559
- * Check if an OTP code is valid format (6 digits)
563
+ * Check if an OTP code is valid format (6 digits).
564
+ *
565
+ * This method:
566
+ * - Trims leading and trailing whitespace from the input before validating.
567
+ * - Requires exactly 6 numeric digits (0–9); any non-numeric characters or
568
+ * incorrect length will cause it to return `false`.
569
+ *
570
+ * Note: This method expects a string value. Passing `null`, `undefined`, or
571
+ * other non-string values will result in a runtime error when `.trim()` is
572
+ * called, rather than returning `false`.
573
+ *
574
+ * @param code - The OTP code to validate.
575
+ * @returns `true` if the trimmed code consists of exactly 6 digits, otherwise `false`.
560
576
  */
561
577
  static isValidOTPCode(code) {
562
578
  return /^\d{6}$/.test(code.trim());
@@ -645,13 +661,25 @@ var TokenStorage = class {
645
661
  constructor() {
646
662
  this.storageKey = "lanonasis_mcp_tokens";
647
663
  this.webEncryptionKeyStorage = "lanonasis_web_token_enc_key";
648
- if (this.isNode()) {
649
- try {
650
- this.keytar = require("keytar");
651
- } catch (e) {
652
- console.warn("Keytar not available - falling back to file storage");
653
- }
664
+ this.keytar = null;
665
+ this.keytarLoadAttempted = false;
666
+ }
667
+ async getKeytar() {
668
+ if (!this.isNode()) {
669
+ return null;
670
+ }
671
+ if (this.keytarLoadAttempted) {
672
+ return this.keytar;
673
+ }
674
+ this.keytarLoadAttempted = true;
675
+ try {
676
+ const keytarModule = await import("keytar");
677
+ this.keytar = keytarModule.default ?? keytarModule;
678
+ } catch {
679
+ this.keytar = null;
680
+ console.warn("Keytar not available - falling back to file storage");
654
681
  }
682
+ return this.keytar;
655
683
  }
656
684
  async store(tokens) {
657
685
  const tokensWithTimestamp = {
@@ -660,8 +688,9 @@ var TokenStorage = class {
660
688
  };
661
689
  const tokenString = JSON.stringify(tokensWithTimestamp);
662
690
  if (this.isNode()) {
663
- if (this.keytar) {
664
- await this.keytar.setPassword("lanonasis-mcp", "tokens", tokenString);
691
+ const keytar = await this.getKeytar();
692
+ if (keytar) {
693
+ await keytar.setPassword("lanonasis-mcp", "tokens", tokenString);
665
694
  } else {
666
695
  await this.storeToFile(tokenString);
667
696
  }
@@ -678,8 +707,9 @@ var TokenStorage = class {
678
707
  let tokenString = null;
679
708
  try {
680
709
  if (this.isNode()) {
681
- if (this.keytar) {
682
- tokenString = await this.keytar.getPassword("lanonasis-mcp", "tokens");
710
+ const keytar = await this.getKeytar();
711
+ if (keytar) {
712
+ tokenString = await keytar.getPassword("lanonasis-mcp", "tokens");
683
713
  }
684
714
  if (!tokenString) {
685
715
  tokenString = await this.retrieveFromFile();
@@ -703,8 +733,9 @@ var TokenStorage = class {
703
733
  }
704
734
  async clear() {
705
735
  if (this.isNode()) {
706
- if (this.keytar) {
707
- await this.keytar.deletePassword("lanonasis-mcp", "tokens");
736
+ const keytar = await this.getKeytar();
737
+ if (keytar) {
738
+ await keytar.deletePassword("lanonasis-mcp", "tokens");
708
739
  }
709
740
  await this.deleteFile();
710
741
  } else if (this.isElectron()) {
@@ -949,14 +980,26 @@ var ApiKeyStorage = class {
949
980
  this.storageKey = "lanonasis_api_key";
950
981
  this.legacyConfigKey = "lanonasis_legacy_api_key";
951
982
  this.webEncryptionKeyStorage = "lanonasis_web_enc_key";
983
+ this.keytar = null;
984
+ this.keytarLoadAttempted = false;
952
985
  this.migrationCompleted = false;
953
- if (this.isNode()) {
954
- try {
955
- this.keytar = require("keytar");
956
- } catch (e) {
957
- console.warn("Keytar not available - falling back to encrypted file storage");
958
- }
986
+ }
987
+ async getKeytar() {
988
+ if (!this.isNode()) {
989
+ return null;
990
+ }
991
+ if (this.keytarLoadAttempted) {
992
+ return this.keytar;
959
993
  }
994
+ this.keytarLoadAttempted = true;
995
+ try {
996
+ const keytarModule = await import("keytar");
997
+ this.keytar = keytarModule.default ?? keytarModule;
998
+ } catch {
999
+ this.keytar = null;
1000
+ console.warn("Keytar not available - falling back to encrypted file storage");
1001
+ }
1002
+ return this.keytar;
960
1003
  }
961
1004
  /**
962
1005
  * Initialize and migrate from legacy storage if needed
@@ -976,9 +1019,10 @@ var ApiKeyStorage = class {
976
1019
  };
977
1020
  const keyString = JSON.stringify(dataWithTimestamp);
978
1021
  if (this.isNode()) {
979
- if (this.keytar) {
1022
+ const keytar = await this.getKeytar();
1023
+ if (keytar) {
980
1024
  try {
981
- await this.keytar.setPassword("lanonasis-mcp", this.storageKey, keyString);
1025
+ await keytar.setPassword("lanonasis-mcp", this.storageKey, keyString);
982
1026
  return;
983
1027
  } catch (error) {
984
1028
  console.warn("Keytar storage failed, falling back to file:", error);
@@ -1001,9 +1045,10 @@ var ApiKeyStorage = class {
1001
1045
  let keyString = null;
1002
1046
  try {
1003
1047
  if (this.isNode()) {
1004
- if (this.keytar) {
1048
+ const keytar = await this.getKeytar();
1049
+ if (keytar) {
1005
1050
  try {
1006
- keyString = await this.keytar.getPassword("lanonasis-mcp", this.storageKey);
1051
+ keyString = await keytar.getPassword("lanonasis-mcp", this.storageKey);
1007
1052
  } catch (error) {
1008
1053
  console.warn("Keytar retrieval failed, trying file:", error);
1009
1054
  }
@@ -1061,9 +1106,10 @@ var ApiKeyStorage = class {
1061
1106
  */
1062
1107
  async clear() {
1063
1108
  if (this.isNode()) {
1064
- if (this.keytar) {
1109
+ const keytar = await this.getKeytar();
1110
+ if (keytar) {
1065
1111
  try {
1066
- await this.keytar.deletePassword("lanonasis-mcp", this.storageKey);
1112
+ await keytar.deletePassword("lanonasis-mcp", this.storageKey);
1067
1113
  } catch (error) {
1068
1114
  console.warn("Keytar deletion failed:", error);
1069
1115
  }
@@ -1365,26 +1411,19 @@ var ApiKeyStorage = class {
1365
1411
  throw new Error("No base64 decoder available");
1366
1412
  }
1367
1413
  /**
1368
- * Normalize API keys to a SHA-256 hex digest.
1369
- * Accepts pre-hashed input and lowercases it to prevent double hashing.
1414
+ * Normalize stored credentials without transforming their value.
1415
+ *
1416
+ * These secrets are stored in encrypted local storage/keychains and must remain
1417
+ * usable for outbound authentication headers (for example X-API-Key or Bearer).
1418
+ * Hashing here would make the original credential unrecoverable and break
1419
+ * clients that need to send the raw key/token back to a remote service.
1370
1420
  */
1371
1421
  async normalizeApiKey(apiKey) {
1372
1422
  const value = apiKey?.trim();
1373
1423
  if (!value) {
1374
1424
  throw new Error("API key must be a non-empty string");
1375
1425
  }
1376
- if (/^[a-f0-9]{64}$/i.test(value)) {
1377
- return value.toLowerCase();
1378
- }
1379
- if (typeof globalThis !== "undefined" && globalThis.crypto?.subtle) {
1380
- const encoder = new TextEncoder();
1381
- const data = encoder.encode(value);
1382
- const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", data);
1383
- const hashArray = Array.from(new Uint8Array(hashBuffer));
1384
- return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
1385
- }
1386
- const nodeCrypto = await import("crypto");
1387
- return nodeCrypto.createHash("sha256").update(value).digest("hex");
1426
+ return value;
1388
1427
  }
1389
1428
  };
1390
1429
 
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BaseOAuthFlow, O as OAuthConfig, c as TokenResponse, T as TokenStorageAdapter } from './api-key-storage-web-J3W8nQi2.cjs';
2
- export { l as ApiKeyData, m as ApiKeyStorage, b as ApiKeyStorageWeb, e as AuthError, A as AuthGatewayClient, h as AuthGatewayClientConfig, f as AuthTokenType, g as AuthValidationResult, D as DesktopOAuthFlow, d as DeviceCodeResponse, G as GrantType, P as PKCEChallenge, i as TokenExchangeOptions, j as TokenExchangeResponse, k as TokenStorage, a as TokenStorageWeb } from './api-key-storage-web-J3W8nQi2.cjs';
1
+ import { B as BaseOAuthFlow, O as OAuthConfig, i as TokenResponse, T as TokenStorageAdapter } from './api-key-storage-web-DUyiN9mC.cjs';
2
+ export { k as ApiKeyData, l as ApiKeyStorage, A as ApiKeyStorageWeb, a as AuthError, b as AuthGatewayClient, c as AuthGatewayClientConfig, d as AuthTokenType, e as AuthValidationResult, D as DesktopOAuthFlow, f as DeviceCodeResponse, G as GrantType, P as PKCEChallenge, g as TokenExchangeOptions, h as TokenExchangeResponse, m as TokenStorage, j as TokenStorageWeb } from './api-key-storage-web-DUyiN9mC.cjs';
3
3
 
4
4
  declare class TerminalOAuthFlow extends BaseOAuthFlow {
5
5
  private pollInterval;
@@ -131,7 +131,19 @@ declare class MagicLinkFlow extends BaseOAuthFlow {
131
131
  */
132
132
  static isValidEmail(email: string): boolean;
133
133
  /**
134
- * Check if an OTP code is valid format (6 digits)
134
+ * Check if an OTP code is valid format (6 digits).
135
+ *
136
+ * This method:
137
+ * - Trims leading and trailing whitespace from the input before validating.
138
+ * - Requires exactly 6 numeric digits (0–9); any non-numeric characters or
139
+ * incorrect length will cause it to return `false`.
140
+ *
141
+ * Note: This method expects a string value. Passing `null`, `undefined`, or
142
+ * other non-string values will result in a runtime error when `.trim()` is
143
+ * called, rather than returning `false`.
144
+ *
145
+ * @param code - The OTP code to validate.
146
+ * @returns `true` if the trimmed code consists of exactly 6 digits, otherwise `false`.
135
147
  */
136
148
  static isValidOTPCode(code: string): boolean;
137
149
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BaseOAuthFlow, O as OAuthConfig, c as TokenResponse, T as TokenStorageAdapter } from './api-key-storage-web-J3W8nQi2.js';
2
- export { l as ApiKeyData, m as ApiKeyStorage, b as ApiKeyStorageWeb, e as AuthError, A as AuthGatewayClient, h as AuthGatewayClientConfig, f as AuthTokenType, g as AuthValidationResult, D as DesktopOAuthFlow, d as DeviceCodeResponse, G as GrantType, P as PKCEChallenge, i as TokenExchangeOptions, j as TokenExchangeResponse, k as TokenStorage, a as TokenStorageWeb } from './api-key-storage-web-J3W8nQi2.js';
1
+ import { B as BaseOAuthFlow, O as OAuthConfig, i as TokenResponse, T as TokenStorageAdapter } from './api-key-storage-web-DUyiN9mC.js';
2
+ export { k as ApiKeyData, l as ApiKeyStorage, A as ApiKeyStorageWeb, a as AuthError, b as AuthGatewayClient, c as AuthGatewayClientConfig, d as AuthTokenType, e as AuthValidationResult, D as DesktopOAuthFlow, f as DeviceCodeResponse, G as GrantType, P as PKCEChallenge, g as TokenExchangeOptions, h as TokenExchangeResponse, m as TokenStorage, j as TokenStorageWeb } from './api-key-storage-web-DUyiN9mC.js';
3
3
 
4
4
  declare class TerminalOAuthFlow extends BaseOAuthFlow {
5
5
  private pollInterval;
@@ -131,7 +131,19 @@ declare class MagicLinkFlow extends BaseOAuthFlow {
131
131
  */
132
132
  static isValidEmail(email: string): boolean;
133
133
  /**
134
- * Check if an OTP code is valid format (6 digits)
134
+ * Check if an OTP code is valid format (6 digits).
135
+ *
136
+ * This method:
137
+ * - Trims leading and trailing whitespace from the input before validating.
138
+ * - Requires exactly 6 numeric digits (0–9); any non-numeric characters or
139
+ * incorrect length will cause it to return `false`.
140
+ *
141
+ * Note: This method expects a string value. Passing `null`, `undefined`, or
142
+ * other non-string values will result in a runtime error when `.trim()` is
143
+ * called, rather than returning `false`.
144
+ *
145
+ * @param code - The OTP code to validate.
146
+ * @returns `true` if the trimmed code consists of exactly 6 digits, otherwise `false`.
135
147
  */
136
148
  static isValidOTPCode(code: string): boolean;
137
149
  }
package/dist/index.mjs CHANGED
@@ -359,11 +359,15 @@ var MagicLinkFlow = class extends BaseOAuthFlow {
359
359
  * @returns Response with success status and expiration time
360
360
  */
361
361
  async requestOTP(email) {
362
+ if (typeof email !== "string" || !email.trim()) {
363
+ throw new Error("Invalid email: a non-empty email address is required to request an OTP.");
364
+ }
365
+ const normalizedEmail = email.trim().toLowerCase();
362
366
  const response = await fetch3(`${this.authBaseUrl}/v1/auth/otp/send`, {
363
367
  method: "POST",
364
368
  headers: { "Content-Type": "application/json" },
365
369
  body: JSON.stringify({
366
- email: email.trim().toLowerCase(),
370
+ email: normalizedEmail,
367
371
  type: "email",
368
372
  // Explicitly request 6-digit code, not magic link
369
373
  platform: this.platform,
@@ -517,7 +521,19 @@ var MagicLinkFlow = class extends BaseOAuthFlow {
517
521
  return emailRegex.test(email.trim());
518
522
  }
519
523
  /**
520
- * Check if an OTP code is valid format (6 digits)
524
+ * Check if an OTP code is valid format (6 digits).
525
+ *
526
+ * This method:
527
+ * - Trims leading and trailing whitespace from the input before validating.
528
+ * - Requires exactly 6 numeric digits (0–9); any non-numeric characters or
529
+ * incorrect length will cause it to return `false`.
530
+ *
531
+ * Note: This method expects a string value. Passing `null`, `undefined`, or
532
+ * other non-string values will result in a runtime error when `.trim()` is
533
+ * called, rather than returning `false`.
534
+ *
535
+ * @param code - The OTP code to validate.
536
+ * @returns `true` if the trimmed code consists of exactly 6 digits, otherwise `false`.
521
537
  */
522
538
  static isValidOTPCode(code) {
523
539
  return /^\d{6}$/.test(code.trim());
@@ -606,13 +622,25 @@ var TokenStorage = class {
606
622
  constructor() {
607
623
  this.storageKey = "lanonasis_mcp_tokens";
608
624
  this.webEncryptionKeyStorage = "lanonasis_web_token_enc_key";
609
- if (this.isNode()) {
610
- try {
611
- this.keytar = __require("keytar");
612
- } catch (e) {
613
- console.warn("Keytar not available - falling back to file storage");
614
- }
625
+ this.keytar = null;
626
+ this.keytarLoadAttempted = false;
627
+ }
628
+ async getKeytar() {
629
+ if (!this.isNode()) {
630
+ return null;
631
+ }
632
+ if (this.keytarLoadAttempted) {
633
+ return this.keytar;
634
+ }
635
+ this.keytarLoadAttempted = true;
636
+ try {
637
+ const keytarModule = await import("keytar");
638
+ this.keytar = keytarModule.default ?? keytarModule;
639
+ } catch {
640
+ this.keytar = null;
641
+ console.warn("Keytar not available - falling back to file storage");
615
642
  }
643
+ return this.keytar;
616
644
  }
617
645
  async store(tokens) {
618
646
  const tokensWithTimestamp = {
@@ -621,8 +649,9 @@ var TokenStorage = class {
621
649
  };
622
650
  const tokenString = JSON.stringify(tokensWithTimestamp);
623
651
  if (this.isNode()) {
624
- if (this.keytar) {
625
- await this.keytar.setPassword("lanonasis-mcp", "tokens", tokenString);
652
+ const keytar = await this.getKeytar();
653
+ if (keytar) {
654
+ await keytar.setPassword("lanonasis-mcp", "tokens", tokenString);
626
655
  } else {
627
656
  await this.storeToFile(tokenString);
628
657
  }
@@ -639,8 +668,9 @@ var TokenStorage = class {
639
668
  let tokenString = null;
640
669
  try {
641
670
  if (this.isNode()) {
642
- if (this.keytar) {
643
- tokenString = await this.keytar.getPassword("lanonasis-mcp", "tokens");
671
+ const keytar = await this.getKeytar();
672
+ if (keytar) {
673
+ tokenString = await keytar.getPassword("lanonasis-mcp", "tokens");
644
674
  }
645
675
  if (!tokenString) {
646
676
  tokenString = await this.retrieveFromFile();
@@ -664,8 +694,9 @@ var TokenStorage = class {
664
694
  }
665
695
  async clear() {
666
696
  if (this.isNode()) {
667
- if (this.keytar) {
668
- await this.keytar.deletePassword("lanonasis-mcp", "tokens");
697
+ const keytar = await this.getKeytar();
698
+ if (keytar) {
699
+ await keytar.deletePassword("lanonasis-mcp", "tokens");
669
700
  }
670
701
  await this.deleteFile();
671
702
  } else if (this.isElectron()) {
@@ -910,14 +941,26 @@ var ApiKeyStorage = class {
910
941
  this.storageKey = "lanonasis_api_key";
911
942
  this.legacyConfigKey = "lanonasis_legacy_api_key";
912
943
  this.webEncryptionKeyStorage = "lanonasis_web_enc_key";
944
+ this.keytar = null;
945
+ this.keytarLoadAttempted = false;
913
946
  this.migrationCompleted = false;
914
- if (this.isNode()) {
915
- try {
916
- this.keytar = __require("keytar");
917
- } catch (e) {
918
- console.warn("Keytar not available - falling back to encrypted file storage");
919
- }
947
+ }
948
+ async getKeytar() {
949
+ if (!this.isNode()) {
950
+ return null;
951
+ }
952
+ if (this.keytarLoadAttempted) {
953
+ return this.keytar;
920
954
  }
955
+ this.keytarLoadAttempted = true;
956
+ try {
957
+ const keytarModule = await import("keytar");
958
+ this.keytar = keytarModule.default ?? keytarModule;
959
+ } catch {
960
+ this.keytar = null;
961
+ console.warn("Keytar not available - falling back to encrypted file storage");
962
+ }
963
+ return this.keytar;
921
964
  }
922
965
  /**
923
966
  * Initialize and migrate from legacy storage if needed
@@ -937,9 +980,10 @@ var ApiKeyStorage = class {
937
980
  };
938
981
  const keyString = JSON.stringify(dataWithTimestamp);
939
982
  if (this.isNode()) {
940
- if (this.keytar) {
983
+ const keytar = await this.getKeytar();
984
+ if (keytar) {
941
985
  try {
942
- await this.keytar.setPassword("lanonasis-mcp", this.storageKey, keyString);
986
+ await keytar.setPassword("lanonasis-mcp", this.storageKey, keyString);
943
987
  return;
944
988
  } catch (error) {
945
989
  console.warn("Keytar storage failed, falling back to file:", error);
@@ -962,9 +1006,10 @@ var ApiKeyStorage = class {
962
1006
  let keyString = null;
963
1007
  try {
964
1008
  if (this.isNode()) {
965
- if (this.keytar) {
1009
+ const keytar = await this.getKeytar();
1010
+ if (keytar) {
966
1011
  try {
967
- keyString = await this.keytar.getPassword("lanonasis-mcp", this.storageKey);
1012
+ keyString = await keytar.getPassword("lanonasis-mcp", this.storageKey);
968
1013
  } catch (error) {
969
1014
  console.warn("Keytar retrieval failed, trying file:", error);
970
1015
  }
@@ -1022,9 +1067,10 @@ var ApiKeyStorage = class {
1022
1067
  */
1023
1068
  async clear() {
1024
1069
  if (this.isNode()) {
1025
- if (this.keytar) {
1070
+ const keytar = await this.getKeytar();
1071
+ if (keytar) {
1026
1072
  try {
1027
- await this.keytar.deletePassword("lanonasis-mcp", this.storageKey);
1073
+ await keytar.deletePassword("lanonasis-mcp", this.storageKey);
1028
1074
  } catch (error) {
1029
1075
  console.warn("Keytar deletion failed:", error);
1030
1076
  }
@@ -1326,26 +1372,19 @@ var ApiKeyStorage = class {
1326
1372
  throw new Error("No base64 decoder available");
1327
1373
  }
1328
1374
  /**
1329
- * Normalize API keys to a SHA-256 hex digest.
1330
- * Accepts pre-hashed input and lowercases it to prevent double hashing.
1375
+ * Normalize stored credentials without transforming their value.
1376
+ *
1377
+ * These secrets are stored in encrypted local storage/keychains and must remain
1378
+ * usable for outbound authentication headers (for example X-API-Key or Bearer).
1379
+ * Hashing here would make the original credential unrecoverable and break
1380
+ * clients that need to send the raw key/token back to a remote service.
1331
1381
  */
1332
1382
  async normalizeApiKey(apiKey) {
1333
1383
  const value = apiKey?.trim();
1334
1384
  if (!value) {
1335
1385
  throw new Error("API key must be a non-empty string");
1336
1386
  }
1337
- if (/^[a-f0-9]{64}$/i.test(value)) {
1338
- return value.toLowerCase();
1339
- }
1340
- if (typeof globalThis !== "undefined" && globalThis.crypto?.subtle) {
1341
- const encoder = new TextEncoder();
1342
- const data = encoder.encode(value);
1343
- const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", data);
1344
- const hashArray = Array.from(new Uint8Array(hashBuffer));
1345
- return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
1346
- }
1347
- const nodeCrypto = await import("crypto");
1348
- return nodeCrypto.createHash("sha256").update(value).digest("hex");
1387
+ return value;
1349
1388
  }
1350
1389
  };
1351
1390