@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/README.md +183 -15
- package/dist/{api-key-storage-web-J3W8nQi2.d.cts → api-key-storage-web-DUyiN9mC.d.cts} +11 -5
- package/dist/{api-key-storage-web-J3W8nQi2.d.ts → api-key-storage-web-DUyiN9mC.d.ts} +11 -5
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/constants-BZPTHasL.d.cts +110 -0
- package/dist/constants-BZPTHasL.d.ts +110 -0
- package/dist/index.cjs +79 -40
- package/dist/index.d.cts +15 -3
- package/dist/index.d.ts +15 -3
- package/dist/index.mjs +79 -40
- package/dist/react/index.cjs +261 -0
- package/dist/react/index.d.cts +95 -0
- package/dist/react/index.d.ts +95 -0
- package/dist/react/index.mjs +238 -0
- package/dist/server/index.cjs +169 -0
- package/dist/server/index.d.cts +184 -0
- package/dist/server/index.d.ts +184 -0
- package/dist/server/index.mjs +146 -0
- package/package.json +57 -15
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:
|
|
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
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
|
|
664
|
-
|
|
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
|
-
|
|
682
|
-
|
|
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
|
-
|
|
707
|
-
|
|
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
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
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
|
-
|
|
1022
|
+
const keytar = await this.getKeytar();
|
|
1023
|
+
if (keytar) {
|
|
980
1024
|
try {
|
|
981
|
-
await
|
|
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
|
-
|
|
1048
|
+
const keytar = await this.getKeytar();
|
|
1049
|
+
if (keytar) {
|
|
1005
1050
|
try {
|
|
1006
|
-
keyString = await
|
|
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
|
-
|
|
1109
|
+
const keytar = await this.getKeytar();
|
|
1110
|
+
if (keytar) {
|
|
1065
1111
|
try {
|
|
1066
|
-
await
|
|
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
|
|
1369
|
-
*
|
|
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
|
-
|
|
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,
|
|
2
|
-
export {
|
|
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,
|
|
2
|
-
export {
|
|
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:
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
|
|
625
|
-
|
|
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
|
-
|
|
643
|
-
|
|
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
|
-
|
|
668
|
-
|
|
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
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
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
|
-
|
|
983
|
+
const keytar = await this.getKeytar();
|
|
984
|
+
if (keytar) {
|
|
941
985
|
try {
|
|
942
|
-
await
|
|
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
|
-
|
|
1009
|
+
const keytar = await this.getKeytar();
|
|
1010
|
+
if (keytar) {
|
|
966
1011
|
try {
|
|
967
|
-
keyString = await
|
|
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
|
-
|
|
1070
|
+
const keytar = await this.getKeytar();
|
|
1071
|
+
if (keytar) {
|
|
1026
1072
|
try {
|
|
1027
|
-
await
|
|
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
|
|
1330
|
-
*
|
|
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
|
-
|
|
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
|
|