@leanmcp/auth 0.4.4-alpha.6.6dae082 → 0.4.4-alpha.67.ad9d43a
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/{auth0-DWCHZ7IN.mjs → auth0-ABDAL2B7.mjs} +1 -1
- package/dist/{chunk-ZJYMG6ZM.mjs → chunk-L2EFAPCF.mjs} +11 -5
- package/dist/{clerk-YVTZMRLF.mjs → clerk-Z3T5NM5E.mjs} +1 -1
- package/dist/{cognito-XKPEG6UH.mjs → cognito-SL5RPP2Y.mjs} +1 -1
- package/dist/firebase-ZVHBNZ3E.mjs +228 -0
- package/dist/index.js +247 -11
- package/dist/index.mjs +1 -1
- package/dist/{leanmcp-73RUGZ2B.mjs → leanmcp-VW5635QY.mjs} +1 -1
- package/package.json +4 -4
|
@@ -176,31 +176,37 @@ var AuthProvider = class extends AuthProviderBase {
|
|
|
176
176
|
const finalConfig = config || this.config;
|
|
177
177
|
switch (this.providerType) {
|
|
178
178
|
case "cognito": {
|
|
179
|
-
const { AuthCognito } = await import("./cognito-
|
|
179
|
+
const { AuthCognito } = await import("./cognito-SL5RPP2Y.mjs");
|
|
180
180
|
this.providerInstance = new AuthCognito();
|
|
181
181
|
await this.providerInstance.init(finalConfig);
|
|
182
182
|
break;
|
|
183
183
|
}
|
|
184
184
|
case "auth0": {
|
|
185
|
-
const { AuthAuth0 } = await import("./auth0-
|
|
185
|
+
const { AuthAuth0 } = await import("./auth0-ABDAL2B7.mjs");
|
|
186
186
|
this.providerInstance = new AuthAuth0();
|
|
187
187
|
await this.providerInstance.init(finalConfig);
|
|
188
188
|
break;
|
|
189
189
|
}
|
|
190
190
|
case "clerk": {
|
|
191
|
-
const { AuthClerk } = await import("./clerk-
|
|
191
|
+
const { AuthClerk } = await import("./clerk-Z3T5NM5E.mjs");
|
|
192
192
|
this.providerInstance = new AuthClerk();
|
|
193
193
|
await this.providerInstance.init(finalConfig);
|
|
194
194
|
break;
|
|
195
195
|
}
|
|
196
|
+
case "firebase": {
|
|
197
|
+
const { AuthFirebase } = await import("./firebase-ZVHBNZ3E.mjs");
|
|
198
|
+
this.providerInstance = new AuthFirebase();
|
|
199
|
+
await this.providerInstance.init(finalConfig);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
196
202
|
case "leanmcp": {
|
|
197
|
-
const { AuthLeanmcp } = await import("./leanmcp-
|
|
203
|
+
const { AuthLeanmcp } = await import("./leanmcp-VW5635QY.mjs");
|
|
198
204
|
this.providerInstance = new AuthLeanmcp();
|
|
199
205
|
await this.providerInstance.init(finalConfig);
|
|
200
206
|
break;
|
|
201
207
|
}
|
|
202
208
|
default:
|
|
203
|
-
throw new Error(`Unsupported auth provider: ${this.providerType}. Supported providers: cognito, auth0, clerk, leanmcp`);
|
|
209
|
+
throw new Error(`Unsupported auth provider: ${this.providerType}. Supported providers: cognito, auth0, clerk, firebase, leanmcp`);
|
|
204
210
|
}
|
|
205
211
|
}
|
|
206
212
|
/**
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthProviderBase
|
|
3
|
+
} from "./chunk-L2EFAPCF.mjs";
|
|
4
|
+
import {
|
|
5
|
+
__name
|
|
6
|
+
} from "./chunk-LPEX4YW6.mjs";
|
|
7
|
+
|
|
8
|
+
// src/providers/firebase.ts
|
|
9
|
+
import axios from "axios";
|
|
10
|
+
import jwt from "jsonwebtoken";
|
|
11
|
+
import jwkToPem from "jwk-to-pem";
|
|
12
|
+
var AuthFirebase = class extends AuthProviderBase {
|
|
13
|
+
static {
|
|
14
|
+
__name(this, "AuthFirebase");
|
|
15
|
+
}
|
|
16
|
+
projectId = "";
|
|
17
|
+
apiKey = "";
|
|
18
|
+
jwksCache = null;
|
|
19
|
+
jwksCacheExpiry = 0;
|
|
20
|
+
/**
|
|
21
|
+
* Google's public keys URL for Firebase token verification
|
|
22
|
+
* These rotate regularly, so we cache with respect to Cache-Control headers
|
|
23
|
+
*/
|
|
24
|
+
GOOGLE_JWKS_URL = "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com";
|
|
25
|
+
/**
|
|
26
|
+
* Firebase Auth REST API for token refresh
|
|
27
|
+
* Docs: https://firebase.google.com/docs/reference/rest/auth
|
|
28
|
+
*/
|
|
29
|
+
SECURE_TOKEN_URL = "https://securetoken.googleapis.com/v1/token";
|
|
30
|
+
/**
|
|
31
|
+
* Initialize Firebase Auth Provider
|
|
32
|
+
*
|
|
33
|
+
* @param config - Configuration options
|
|
34
|
+
* @param config.projectId - Firebase project ID (required)
|
|
35
|
+
* @param config.apiKey - Firebase Web API key (required for token refresh)
|
|
36
|
+
*/
|
|
37
|
+
async init(config) {
|
|
38
|
+
this.projectId = config?.projectId || process.env.FIREBASE_PROJECT_ID || "";
|
|
39
|
+
this.apiKey = config?.apiKey || process.env.FIREBASE_API_KEY || "";
|
|
40
|
+
if (!this.projectId) {
|
|
41
|
+
throw new Error("Firebase config missing: projectId is required. Provide it in config or set FIREBASE_PROJECT_ID environment variable.");
|
|
42
|
+
}
|
|
43
|
+
if (!this.apiKey) {
|
|
44
|
+
console.warn("[Firebase Auth] API key not provided. Token refresh will not be available. Set FIREBASE_API_KEY environment variable or pass apiKey in config.");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Refresh an authentication token using Firebase's secure token API
|
|
49
|
+
*
|
|
50
|
+
* @param refreshToken - The refresh token obtained during sign-in
|
|
51
|
+
* @returns New tokens including access_token, id_token, and refresh_token
|
|
52
|
+
*/
|
|
53
|
+
async refreshToken(refreshToken) {
|
|
54
|
+
if (!this.apiKey) {
|
|
55
|
+
throw new Error("Firebase API key is required for token refresh. Set FIREBASE_API_KEY environment variable or pass apiKey in config.");
|
|
56
|
+
}
|
|
57
|
+
const url = `${this.SECURE_TOKEN_URL}?key=${this.apiKey}`;
|
|
58
|
+
try {
|
|
59
|
+
const { data } = await axios.post(url, {
|
|
60
|
+
grant_type: "refresh_token",
|
|
61
|
+
refresh_token: refreshToken
|
|
62
|
+
}, {
|
|
63
|
+
headers: {
|
|
64
|
+
"Content-Type": "application/json"
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
access_token: data.access_token,
|
|
69
|
+
id_token: data.id_token,
|
|
70
|
+
refresh_token: data.refresh_token,
|
|
71
|
+
expires_in: parseInt(data.expires_in, 10),
|
|
72
|
+
token_type: data.token_type || "Bearer",
|
|
73
|
+
user_id: data.user_id
|
|
74
|
+
};
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (axios.isAxiosError(error) && error.response?.data?.error) {
|
|
77
|
+
const firebaseError = error.response.data.error;
|
|
78
|
+
throw new Error(`Firebase token refresh failed: ${firebaseError.message || firebaseError.code || "Unknown error"}`);
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Verify if a Firebase ID token is valid
|
|
85
|
+
*
|
|
86
|
+
* Verification includes:
|
|
87
|
+
* - Signature validation using Google's public keys
|
|
88
|
+
* - Expiration check
|
|
89
|
+
* - Issuer validation (must be from your Firebase project)
|
|
90
|
+
* - Audience validation (must match your project ID)
|
|
91
|
+
*
|
|
92
|
+
* @param token - The Firebase ID token to verify
|
|
93
|
+
* @returns true if valid, throws error otherwise
|
|
94
|
+
*/
|
|
95
|
+
async verifyToken(token) {
|
|
96
|
+
try {
|
|
97
|
+
await this.verifyJwt(token);
|
|
98
|
+
return true;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (error instanceof Error) {
|
|
101
|
+
if (error.message.includes("jwt expired")) {
|
|
102
|
+
throw new Error("Token has expired");
|
|
103
|
+
}
|
|
104
|
+
if (error.message.includes("invalid signature")) {
|
|
105
|
+
throw new Error("Invalid token signature");
|
|
106
|
+
}
|
|
107
|
+
if (error.message.includes("jwt malformed")) {
|
|
108
|
+
throw new Error("Malformed token");
|
|
109
|
+
}
|
|
110
|
+
if (error.message.includes("invalid issuer")) {
|
|
111
|
+
throw new Error("Invalid token issuer - token not from expected Firebase project");
|
|
112
|
+
}
|
|
113
|
+
if (error.message.includes("invalid audience")) {
|
|
114
|
+
throw new Error("Invalid token audience - token not intended for this project");
|
|
115
|
+
}
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Extract user information from a Firebase ID token
|
|
123
|
+
*
|
|
124
|
+
* @param idToken - The Firebase ID token
|
|
125
|
+
* @returns Normalized user object with common fields
|
|
126
|
+
*/
|
|
127
|
+
async getUser(idToken) {
|
|
128
|
+
const decoded = jwt.decode(idToken);
|
|
129
|
+
if (!decoded) {
|
|
130
|
+
throw new Error("Invalid ID token - unable to decode");
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
// Standard claims
|
|
134
|
+
sub: decoded.sub,
|
|
135
|
+
email: decoded.email,
|
|
136
|
+
email_verified: decoded.email_verified,
|
|
137
|
+
// Firebase-specific identifiers
|
|
138
|
+
uid: decoded.user_id || decoded.sub,
|
|
139
|
+
firebase_uid: decoded.user_id || decoded.sub,
|
|
140
|
+
// User profile
|
|
141
|
+
name: decoded.name,
|
|
142
|
+
picture: decoded.picture,
|
|
143
|
+
phone_number: decoded.phone_number,
|
|
144
|
+
// Provider info
|
|
145
|
+
sign_in_provider: decoded.firebase?.sign_in_provider,
|
|
146
|
+
identities: decoded.firebase?.identities,
|
|
147
|
+
// Token metadata
|
|
148
|
+
auth_time: decoded.auth_time,
|
|
149
|
+
iat: decoded.iat,
|
|
150
|
+
exp: decoded.exp,
|
|
151
|
+
// Full token for access to any custom claims
|
|
152
|
+
attributes: decoded
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Fetch Google's public keys for Firebase token verification
|
|
157
|
+
* Keys are cached based on Cache-Control headers
|
|
158
|
+
*/
|
|
159
|
+
async fetchJWKS() {
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
if (this.jwksCache && now < this.jwksCacheExpiry) {
|
|
162
|
+
return this.jwksCache;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const response = await axios.get(this.GOOGLE_JWKS_URL);
|
|
166
|
+
this.jwksCache = response.data.keys;
|
|
167
|
+
const cacheControl = response.headers["cache-control"];
|
|
168
|
+
if (cacheControl) {
|
|
169
|
+
const maxAgeMatch = cacheControl.match(/max-age=(\d+)/);
|
|
170
|
+
if (maxAgeMatch) {
|
|
171
|
+
const maxAge = parseInt(maxAgeMatch[1], 10) * 1e3;
|
|
172
|
+
this.jwksCacheExpiry = now + maxAge;
|
|
173
|
+
} else {
|
|
174
|
+
this.jwksCacheExpiry = now + 3600 * 1e3;
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
this.jwksCacheExpiry = now + 3600 * 1e3;
|
|
178
|
+
}
|
|
179
|
+
return this.jwksCache;
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (this.jwksCache) {
|
|
182
|
+
console.warn("[Firebase Auth] Failed to refresh JWKS, using cached keys");
|
|
183
|
+
return this.jwksCache;
|
|
184
|
+
}
|
|
185
|
+
throw new Error("Failed to fetch Google public keys for Firebase token verification");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Verify JWT token using Google's public keys
|
|
190
|
+
*/
|
|
191
|
+
async verifyJwt(token) {
|
|
192
|
+
const decoded = jwt.decode(token, {
|
|
193
|
+
complete: true
|
|
194
|
+
});
|
|
195
|
+
if (!decoded) {
|
|
196
|
+
throw new Error("Invalid token - unable to decode");
|
|
197
|
+
}
|
|
198
|
+
const jwks = await this.fetchJWKS();
|
|
199
|
+
const key = jwks.find((k) => k.kid === decoded.header.kid);
|
|
200
|
+
if (!key) {
|
|
201
|
+
this.jwksCache = null;
|
|
202
|
+
const refreshedJwks = await this.fetchJWKS();
|
|
203
|
+
const refreshedKey = refreshedJwks.find((k) => k.kid === decoded.header.kid);
|
|
204
|
+
if (!refreshedKey) {
|
|
205
|
+
throw new Error("Signing key not found in Google JWKS - token may be invalid or keys rotated");
|
|
206
|
+
}
|
|
207
|
+
const pem2 = jwkToPem(refreshedKey);
|
|
208
|
+
return this.verifyWithPem(token, pem2);
|
|
209
|
+
}
|
|
210
|
+
const pem = jwkToPem(key);
|
|
211
|
+
return this.verifyWithPem(token, pem);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Verify token with PEM key and validate claims
|
|
215
|
+
*/
|
|
216
|
+
verifyWithPem(token, pem) {
|
|
217
|
+
return jwt.verify(token, pem, {
|
|
218
|
+
algorithms: [
|
|
219
|
+
"RS256"
|
|
220
|
+
],
|
|
221
|
+
issuer: `https://securetoken.google.com/${this.projectId}`,
|
|
222
|
+
audience: this.projectId
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
export {
|
|
227
|
+
AuthFirebase
|
|
228
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -557,17 +557,247 @@ var init_clerk = __esm({
|
|
|
557
557
|
}
|
|
558
558
|
});
|
|
559
559
|
|
|
560
|
+
// src/providers/firebase.ts
|
|
561
|
+
var firebase_exports = {};
|
|
562
|
+
__export(firebase_exports, {
|
|
563
|
+
AuthFirebase: () => AuthFirebase
|
|
564
|
+
});
|
|
565
|
+
var import_axios4, import_jsonwebtoken4, import_jwk_to_pem4, AuthFirebase;
|
|
566
|
+
var init_firebase = __esm({
|
|
567
|
+
"src/providers/firebase.ts"() {
|
|
568
|
+
"use strict";
|
|
569
|
+
import_axios4 = __toESM(require("axios"));
|
|
570
|
+
import_jsonwebtoken4 = __toESM(require("jsonwebtoken"));
|
|
571
|
+
import_jwk_to_pem4 = __toESM(require("jwk-to-pem"));
|
|
572
|
+
init_index();
|
|
573
|
+
AuthFirebase = class extends AuthProviderBase {
|
|
574
|
+
static {
|
|
575
|
+
__name(this, "AuthFirebase");
|
|
576
|
+
}
|
|
577
|
+
projectId = "";
|
|
578
|
+
apiKey = "";
|
|
579
|
+
jwksCache = null;
|
|
580
|
+
jwksCacheExpiry = 0;
|
|
581
|
+
/**
|
|
582
|
+
* Google's public keys URL for Firebase token verification
|
|
583
|
+
* These rotate regularly, so we cache with respect to Cache-Control headers
|
|
584
|
+
*/
|
|
585
|
+
GOOGLE_JWKS_URL = "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com";
|
|
586
|
+
/**
|
|
587
|
+
* Firebase Auth REST API for token refresh
|
|
588
|
+
* Docs: https://firebase.google.com/docs/reference/rest/auth
|
|
589
|
+
*/
|
|
590
|
+
SECURE_TOKEN_URL = "https://securetoken.googleapis.com/v1/token";
|
|
591
|
+
/**
|
|
592
|
+
* Initialize Firebase Auth Provider
|
|
593
|
+
*
|
|
594
|
+
* @param config - Configuration options
|
|
595
|
+
* @param config.projectId - Firebase project ID (required)
|
|
596
|
+
* @param config.apiKey - Firebase Web API key (required for token refresh)
|
|
597
|
+
*/
|
|
598
|
+
async init(config) {
|
|
599
|
+
this.projectId = config?.projectId || process.env.FIREBASE_PROJECT_ID || "";
|
|
600
|
+
this.apiKey = config?.apiKey || process.env.FIREBASE_API_KEY || "";
|
|
601
|
+
if (!this.projectId) {
|
|
602
|
+
throw new Error("Firebase config missing: projectId is required. Provide it in config or set FIREBASE_PROJECT_ID environment variable.");
|
|
603
|
+
}
|
|
604
|
+
if (!this.apiKey) {
|
|
605
|
+
console.warn("[Firebase Auth] API key not provided. Token refresh will not be available. Set FIREBASE_API_KEY environment variable or pass apiKey in config.");
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Refresh an authentication token using Firebase's secure token API
|
|
610
|
+
*
|
|
611
|
+
* @param refreshToken - The refresh token obtained during sign-in
|
|
612
|
+
* @returns New tokens including access_token, id_token, and refresh_token
|
|
613
|
+
*/
|
|
614
|
+
async refreshToken(refreshToken) {
|
|
615
|
+
if (!this.apiKey) {
|
|
616
|
+
throw new Error("Firebase API key is required for token refresh. Set FIREBASE_API_KEY environment variable or pass apiKey in config.");
|
|
617
|
+
}
|
|
618
|
+
const url = `${this.SECURE_TOKEN_URL}?key=${this.apiKey}`;
|
|
619
|
+
try {
|
|
620
|
+
const { data } = await import_axios4.default.post(url, {
|
|
621
|
+
grant_type: "refresh_token",
|
|
622
|
+
refresh_token: refreshToken
|
|
623
|
+
}, {
|
|
624
|
+
headers: {
|
|
625
|
+
"Content-Type": "application/json"
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
return {
|
|
629
|
+
access_token: data.access_token,
|
|
630
|
+
id_token: data.id_token,
|
|
631
|
+
refresh_token: data.refresh_token,
|
|
632
|
+
expires_in: parseInt(data.expires_in, 10),
|
|
633
|
+
token_type: data.token_type || "Bearer",
|
|
634
|
+
user_id: data.user_id
|
|
635
|
+
};
|
|
636
|
+
} catch (error) {
|
|
637
|
+
if (import_axios4.default.isAxiosError(error) && error.response?.data?.error) {
|
|
638
|
+
const firebaseError = error.response.data.error;
|
|
639
|
+
throw new Error(`Firebase token refresh failed: ${firebaseError.message || firebaseError.code || "Unknown error"}`);
|
|
640
|
+
}
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Verify if a Firebase ID token is valid
|
|
646
|
+
*
|
|
647
|
+
* Verification includes:
|
|
648
|
+
* - Signature validation using Google's public keys
|
|
649
|
+
* - Expiration check
|
|
650
|
+
* - Issuer validation (must be from your Firebase project)
|
|
651
|
+
* - Audience validation (must match your project ID)
|
|
652
|
+
*
|
|
653
|
+
* @param token - The Firebase ID token to verify
|
|
654
|
+
* @returns true if valid, throws error otherwise
|
|
655
|
+
*/
|
|
656
|
+
async verifyToken(token) {
|
|
657
|
+
try {
|
|
658
|
+
await this.verifyJwt(token);
|
|
659
|
+
return true;
|
|
660
|
+
} catch (error) {
|
|
661
|
+
if (error instanceof Error) {
|
|
662
|
+
if (error.message.includes("jwt expired")) {
|
|
663
|
+
throw new Error("Token has expired");
|
|
664
|
+
}
|
|
665
|
+
if (error.message.includes("invalid signature")) {
|
|
666
|
+
throw new Error("Invalid token signature");
|
|
667
|
+
}
|
|
668
|
+
if (error.message.includes("jwt malformed")) {
|
|
669
|
+
throw new Error("Malformed token");
|
|
670
|
+
}
|
|
671
|
+
if (error.message.includes("invalid issuer")) {
|
|
672
|
+
throw new Error("Invalid token issuer - token not from expected Firebase project");
|
|
673
|
+
}
|
|
674
|
+
if (error.message.includes("invalid audience")) {
|
|
675
|
+
throw new Error("Invalid token audience - token not intended for this project");
|
|
676
|
+
}
|
|
677
|
+
throw error;
|
|
678
|
+
}
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Extract user information from a Firebase ID token
|
|
684
|
+
*
|
|
685
|
+
* @param idToken - The Firebase ID token
|
|
686
|
+
* @returns Normalized user object with common fields
|
|
687
|
+
*/
|
|
688
|
+
async getUser(idToken) {
|
|
689
|
+
const decoded = import_jsonwebtoken4.default.decode(idToken);
|
|
690
|
+
if (!decoded) {
|
|
691
|
+
throw new Error("Invalid ID token - unable to decode");
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
// Standard claims
|
|
695
|
+
sub: decoded.sub,
|
|
696
|
+
email: decoded.email,
|
|
697
|
+
email_verified: decoded.email_verified,
|
|
698
|
+
// Firebase-specific identifiers
|
|
699
|
+
uid: decoded.user_id || decoded.sub,
|
|
700
|
+
firebase_uid: decoded.user_id || decoded.sub,
|
|
701
|
+
// User profile
|
|
702
|
+
name: decoded.name,
|
|
703
|
+
picture: decoded.picture,
|
|
704
|
+
phone_number: decoded.phone_number,
|
|
705
|
+
// Provider info
|
|
706
|
+
sign_in_provider: decoded.firebase?.sign_in_provider,
|
|
707
|
+
identities: decoded.firebase?.identities,
|
|
708
|
+
// Token metadata
|
|
709
|
+
auth_time: decoded.auth_time,
|
|
710
|
+
iat: decoded.iat,
|
|
711
|
+
exp: decoded.exp,
|
|
712
|
+
// Full token for access to any custom claims
|
|
713
|
+
attributes: decoded
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Fetch Google's public keys for Firebase token verification
|
|
718
|
+
* Keys are cached based on Cache-Control headers
|
|
719
|
+
*/
|
|
720
|
+
async fetchJWKS() {
|
|
721
|
+
const now = Date.now();
|
|
722
|
+
if (this.jwksCache && now < this.jwksCacheExpiry) {
|
|
723
|
+
return this.jwksCache;
|
|
724
|
+
}
|
|
725
|
+
try {
|
|
726
|
+
const response = await import_axios4.default.get(this.GOOGLE_JWKS_URL);
|
|
727
|
+
this.jwksCache = response.data.keys;
|
|
728
|
+
const cacheControl = response.headers["cache-control"];
|
|
729
|
+
if (cacheControl) {
|
|
730
|
+
const maxAgeMatch = cacheControl.match(/max-age=(\d+)/);
|
|
731
|
+
if (maxAgeMatch) {
|
|
732
|
+
const maxAge = parseInt(maxAgeMatch[1], 10) * 1e3;
|
|
733
|
+
this.jwksCacheExpiry = now + maxAge;
|
|
734
|
+
} else {
|
|
735
|
+
this.jwksCacheExpiry = now + 3600 * 1e3;
|
|
736
|
+
}
|
|
737
|
+
} else {
|
|
738
|
+
this.jwksCacheExpiry = now + 3600 * 1e3;
|
|
739
|
+
}
|
|
740
|
+
return this.jwksCache;
|
|
741
|
+
} catch (error) {
|
|
742
|
+
if (this.jwksCache) {
|
|
743
|
+
console.warn("[Firebase Auth] Failed to refresh JWKS, using cached keys");
|
|
744
|
+
return this.jwksCache;
|
|
745
|
+
}
|
|
746
|
+
throw new Error("Failed to fetch Google public keys for Firebase token verification");
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Verify JWT token using Google's public keys
|
|
751
|
+
*/
|
|
752
|
+
async verifyJwt(token) {
|
|
753
|
+
const decoded = import_jsonwebtoken4.default.decode(token, {
|
|
754
|
+
complete: true
|
|
755
|
+
});
|
|
756
|
+
if (!decoded) {
|
|
757
|
+
throw new Error("Invalid token - unable to decode");
|
|
758
|
+
}
|
|
759
|
+
const jwks = await this.fetchJWKS();
|
|
760
|
+
const key = jwks.find((k) => k.kid === decoded.header.kid);
|
|
761
|
+
if (!key) {
|
|
762
|
+
this.jwksCache = null;
|
|
763
|
+
const refreshedJwks = await this.fetchJWKS();
|
|
764
|
+
const refreshedKey = refreshedJwks.find((k) => k.kid === decoded.header.kid);
|
|
765
|
+
if (!refreshedKey) {
|
|
766
|
+
throw new Error("Signing key not found in Google JWKS - token may be invalid or keys rotated");
|
|
767
|
+
}
|
|
768
|
+
const pem2 = (0, import_jwk_to_pem4.default)(refreshedKey);
|
|
769
|
+
return this.verifyWithPem(token, pem2);
|
|
770
|
+
}
|
|
771
|
+
const pem = (0, import_jwk_to_pem4.default)(key);
|
|
772
|
+
return this.verifyWithPem(token, pem);
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Verify token with PEM key and validate claims
|
|
776
|
+
*/
|
|
777
|
+
verifyWithPem(token, pem) {
|
|
778
|
+
return import_jsonwebtoken4.default.verify(token, pem, {
|
|
779
|
+
algorithms: [
|
|
780
|
+
"RS256"
|
|
781
|
+
],
|
|
782
|
+
issuer: `https://securetoken.google.com/${this.projectId}`,
|
|
783
|
+
audience: this.projectId
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
|
|
560
790
|
// src/providers/leanmcp.ts
|
|
561
791
|
var leanmcp_exports = {};
|
|
562
792
|
__export(leanmcp_exports, {
|
|
563
793
|
AuthLeanmcp: () => AuthLeanmcp
|
|
564
794
|
});
|
|
565
|
-
var
|
|
795
|
+
var import_axios5, import_jsonwebtoken5, AuthLeanmcp;
|
|
566
796
|
var init_leanmcp = __esm({
|
|
567
797
|
"src/providers/leanmcp.ts"() {
|
|
568
798
|
"use strict";
|
|
569
|
-
|
|
570
|
-
|
|
799
|
+
import_axios5 = __toESM(require("axios"));
|
|
800
|
+
import_jsonwebtoken5 = __toESM(require("jsonwebtoken"));
|
|
571
801
|
init_index();
|
|
572
802
|
AuthLeanmcp = class extends AuthProviderBase {
|
|
573
803
|
static {
|
|
@@ -584,7 +814,7 @@ var init_leanmcp = __esm({
|
|
|
584
814
|
async refreshToken(refreshToken) {
|
|
585
815
|
const url = `${this.authUrl}/api/refresh`;
|
|
586
816
|
try {
|
|
587
|
-
const { data } = await
|
|
817
|
+
const { data } = await import_axios5.default.post(url, {
|
|
588
818
|
refresh_token: refreshToken
|
|
589
819
|
}, {
|
|
590
820
|
headers: {
|
|
@@ -593,7 +823,7 @@ var init_leanmcp = __esm({
|
|
|
593
823
|
});
|
|
594
824
|
return data;
|
|
595
825
|
} catch (error) {
|
|
596
|
-
if (
|
|
826
|
+
if (import_axios5.default.isAxiosError(error) && error.response) {
|
|
597
827
|
throw new Error(`Failed to refresh token: ${error.response.data.error?.message || error.response.data.error || "Unknown error"}`);
|
|
598
828
|
}
|
|
599
829
|
throw error;
|
|
@@ -603,7 +833,7 @@ var init_leanmcp = __esm({
|
|
|
603
833
|
if (this.apiKey) {
|
|
604
834
|
const url2 = `${this.orchestrationApiUrl}/public/auth/verify-user`;
|
|
605
835
|
try {
|
|
606
|
-
await
|
|
836
|
+
await import_axios5.default.post(url2, {
|
|
607
837
|
token
|
|
608
838
|
}, {
|
|
609
839
|
headers: {
|
|
@@ -619,7 +849,7 @@ var init_leanmcp = __esm({
|
|
|
619
849
|
}
|
|
620
850
|
const url = `${this.authUrl}/api/verify`;
|
|
621
851
|
try {
|
|
622
|
-
const { data } = await
|
|
852
|
+
const { data } = await import_axios5.default.post(url, {
|
|
623
853
|
token
|
|
624
854
|
}, {
|
|
625
855
|
headers: {
|
|
@@ -635,7 +865,7 @@ var init_leanmcp = __esm({
|
|
|
635
865
|
if (this.apiKey) {
|
|
636
866
|
const url = `${this.orchestrationApiUrl}/public/auth/verify-user`;
|
|
637
867
|
try {
|
|
638
|
-
const { data } = await
|
|
868
|
+
const { data } = await import_axios5.default.post(url, {
|
|
639
869
|
token
|
|
640
870
|
}, {
|
|
641
871
|
headers: {
|
|
@@ -656,7 +886,7 @@ var init_leanmcp = __esm({
|
|
|
656
886
|
throw new Error("Invalid user token or API key");
|
|
657
887
|
}
|
|
658
888
|
}
|
|
659
|
-
const decoded =
|
|
889
|
+
const decoded = import_jsonwebtoken5.default.decode(token);
|
|
660
890
|
if (!decoded) throw new Error("Invalid ID token");
|
|
661
891
|
return {
|
|
662
892
|
uid: decoded.user_id || decoded.sub,
|
|
@@ -683,7 +913,7 @@ var init_leanmcp = __esm({
|
|
|
683
913
|
}
|
|
684
914
|
const url = `${this.orchestrationApiUrl}/public/secrets/user/${projectId}`;
|
|
685
915
|
try {
|
|
686
|
-
const { data } = await
|
|
916
|
+
const { data } = await import_axios5.default.get(url, {
|
|
687
917
|
headers: {
|
|
688
918
|
Authorization: `Bearer ${token}`,
|
|
689
919
|
"x-api-key": this.apiKey
|
|
@@ -757,6 +987,12 @@ var init_index = __esm({
|
|
|
757
987
|
await this.providerInstance.init(finalConfig);
|
|
758
988
|
break;
|
|
759
989
|
}
|
|
990
|
+
case "firebase": {
|
|
991
|
+
const { AuthFirebase: AuthFirebase2 } = await Promise.resolve().then(() => (init_firebase(), firebase_exports));
|
|
992
|
+
this.providerInstance = new AuthFirebase2();
|
|
993
|
+
await this.providerInstance.init(finalConfig);
|
|
994
|
+
break;
|
|
995
|
+
}
|
|
760
996
|
case "leanmcp": {
|
|
761
997
|
const { AuthLeanmcp: AuthLeanmcp2 } = await Promise.resolve().then(() => (init_leanmcp(), leanmcp_exports));
|
|
762
998
|
this.providerInstance = new AuthLeanmcp2();
|
|
@@ -764,7 +1000,7 @@ var init_index = __esm({
|
|
|
764
1000
|
break;
|
|
765
1001
|
}
|
|
766
1002
|
default:
|
|
767
|
-
throw new Error(`Unsupported auth provider: ${this.providerType}. Supported providers: cognito, auth0, clerk, leanmcp`);
|
|
1003
|
+
throw new Error(`Unsupported auth provider: ${this.providerType}. Supported providers: cognito, auth0, clerk, firebase, leanmcp`);
|
|
768
1004
|
}
|
|
769
1005
|
}
|
|
770
1006
|
/**
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leanmcp/auth",
|
|
3
|
-
"version": "0.4.4-alpha.
|
|
3
|
+
"version": "0.4.4-alpha.67.ad9d43a",
|
|
4
4
|
"description": "Authentication and identity module with OAuth 2.1 client, token storage, and multiple providers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"test:watch": "jest --watch"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@leanmcp/core": "^0.
|
|
47
|
+
"@leanmcp/core": "^0.4.0",
|
|
48
48
|
"reflect-metadata": "^0.2.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@leanmcp/env-injection": "^0.1.
|
|
51
|
+
"@leanmcp/env-injection": "^0.1.4",
|
|
52
52
|
"@types/jest": "^29.5.0",
|
|
53
53
|
"@types/jsonwebtoken": "^9.0.10",
|
|
54
54
|
"@types/jwk-to-pem": "^2.0.3",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@aws-sdk/client-cognito-identity-provider": "^3.0.0",
|
|
63
|
-
"@leanmcp/env-injection": "^0.1.
|
|
63
|
+
"@leanmcp/env-injection": "^0.1.4",
|
|
64
64
|
"axios": "^1.0.0",
|
|
65
65
|
"jsonwebtoken": "^9.0.0",
|
|
66
66
|
"jwk-to-pem": "^2.0.0",
|