@leanmcp/auth 0.1.1 → 0.3.0

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.
@@ -0,0 +1,102 @@
1
+ import {
2
+ AuthProviderBase,
3
+ __name
4
+ } from "./chunk-EVD2TRPR.mjs";
5
+
6
+ // src/providers/auth0.ts
7
+ import axios from "axios";
8
+ import jwt from "jsonwebtoken";
9
+ import jwkToPem from "jwk-to-pem";
10
+ var AuthAuth0 = class extends AuthProviderBase {
11
+ static {
12
+ __name(this, "AuthAuth0");
13
+ }
14
+ domain = "";
15
+ clientId = "";
16
+ clientSecret = "";
17
+ audience = "";
18
+ scopes = "openid profile email offline_access";
19
+ jwksCache = null;
20
+ async init(config) {
21
+ this.domain = config?.domain || process.env.AUTH0_DOMAIN || "";
22
+ this.clientId = config?.clientId || process.env.AUTH0_CLIENT_ID || "";
23
+ this.clientSecret = config?.clientSecret || process.env.AUTH0_CLIENT_SECRET || "";
24
+ this.audience = config?.audience || process.env.AUTH0_AUDIENCE || "";
25
+ this.scopes = config?.scopes || this.scopes;
26
+ if (!this.domain || !this.clientId || !this.audience) {
27
+ throw new Error("Auth0 config missing: domain, clientId, and audience are required");
28
+ }
29
+ }
30
+ async refreshToken(refreshToken) {
31
+ const url = `https://${this.domain}/oauth/token`;
32
+ const payload = {
33
+ grant_type: "refresh_token",
34
+ client_id: this.clientId,
35
+ refresh_token: refreshToken,
36
+ audience: this.audience,
37
+ scope: this.scopes
38
+ };
39
+ if (this.clientSecret) {
40
+ payload.client_secret = this.clientSecret;
41
+ }
42
+ const { data } = await axios.post(url, payload, {
43
+ headers: {
44
+ "Content-Type": "application/json"
45
+ }
46
+ });
47
+ return data;
48
+ }
49
+ async verifyToken(token) {
50
+ try {
51
+ await this.verifyJwt(token);
52
+ return true;
53
+ } catch (error) {
54
+ if (error instanceof Error) {
55
+ if (error.message.includes("jwt expired")) throw new Error("Token has expired");
56
+ if (error.message.includes("invalid signature")) throw new Error("Invalid token signature");
57
+ if (error.message.includes("jwt malformed")) throw new Error("Malformed token");
58
+ if (error.message.includes("invalid issuer")) throw new Error("Invalid token issuer");
59
+ throw error;
60
+ }
61
+ return false;
62
+ }
63
+ }
64
+ async getUser(idToken) {
65
+ const decoded = jwt.decode(idToken);
66
+ if (!decoded) throw new Error("Invalid ID token");
67
+ return {
68
+ sub: decoded.sub,
69
+ email: decoded.email,
70
+ email_verified: decoded.email_verified,
71
+ name: decoded.name,
72
+ attributes: decoded
73
+ };
74
+ }
75
+ async fetchJWKS() {
76
+ if (!this.jwksCache) {
77
+ const jwksUri = `https://${this.domain}/.well-known/jwks.json`;
78
+ const { data } = await axios.get(jwksUri);
79
+ this.jwksCache = data.keys;
80
+ }
81
+ return this.jwksCache;
82
+ }
83
+ async verifyJwt(token) {
84
+ const decoded = jwt.decode(token, {
85
+ complete: true
86
+ });
87
+ if (!decoded) throw new Error("Invalid token");
88
+ const jwks = await this.fetchJWKS();
89
+ const key = jwks.find((k) => k.kid === decoded.header.kid);
90
+ if (!key) throw new Error("Signing key not found in JWKS");
91
+ const pem = jwkToPem(key);
92
+ return jwt.verify(token, pem, {
93
+ algorithms: [
94
+ "RS256"
95
+ ],
96
+ issuer: `https://${this.domain}/`
97
+ });
98
+ }
99
+ };
100
+ export {
101
+ AuthAuth0
102
+ };
@@ -6,6 +6,21 @@ import "reflect-metadata";
6
6
 
7
7
  // src/decorators.ts
8
8
  import "reflect-metadata";
9
+ import { AsyncLocalStorage } from "async_hooks";
10
+ var authUserStorage = new AsyncLocalStorage();
11
+ function getAuthUser() {
12
+ return authUserStorage.getStore();
13
+ }
14
+ __name(getAuthUser, "getAuthUser");
15
+ if (typeof globalThis !== "undefined" && !Object.getOwnPropertyDescriptor(globalThis, "authUser")) {
16
+ Object.defineProperty(globalThis, "authUser", {
17
+ get() {
18
+ return authUserStorage.getStore();
19
+ },
20
+ configurable: true,
21
+ enumerable: false
22
+ });
23
+ }
9
24
  var AuthenticationError = class extends Error {
10
25
  static {
11
26
  __name(this, "AuthenticationError");
@@ -16,11 +31,16 @@ var AuthenticationError = class extends Error {
16
31
  this.name = "AuthenticationError";
17
32
  }
18
33
  };
19
- function Authenticated(authProvider) {
34
+ function Authenticated(authProvider, options) {
35
+ const authOptions = {
36
+ getUser: true,
37
+ ...options
38
+ };
20
39
  return function(target, propertyKey, descriptor) {
21
40
  if (!propertyKey && !descriptor) {
22
41
  Reflect.defineMetadata("auth:provider", authProvider, target);
23
42
  Reflect.defineMetadata("auth:required", true, target);
43
+ Reflect.defineMetadata("auth:options", authOptions, target);
24
44
  const prototype = target.prototype;
25
45
  const methodNames = Object.getOwnPropertyNames(prototype).filter((name) => name !== "constructor" && typeof prototype[name] === "function");
26
46
  for (const methodName of methodNames) {
@@ -29,7 +49,8 @@ function Authenticated(authProvider) {
29
49
  const originalMethod = originalDescriptor.value;
30
50
  Reflect.defineMetadata("auth:provider", authProvider, originalMethod);
31
51
  Reflect.defineMetadata("auth:required", true, originalMethod);
32
- prototype[methodName] = createAuthenticatedMethod(originalMethod, authProvider);
52
+ Reflect.defineMetadata("auth:options", authOptions, originalMethod);
53
+ prototype[methodName] = createAuthenticatedMethod(originalMethod, authProvider, authOptions);
33
54
  copyMetadata(originalMethod, prototype[methodName]);
34
55
  }
35
56
  }
@@ -39,7 +60,8 @@ function Authenticated(authProvider) {
39
60
  const originalMethod = descriptor.value;
40
61
  Reflect.defineMetadata("auth:provider", authProvider, originalMethod);
41
62
  Reflect.defineMetadata("auth:required", true, originalMethod);
42
- descriptor.value = createAuthenticatedMethod(originalMethod, authProvider);
63
+ Reflect.defineMetadata("auth:options", authOptions, originalMethod);
64
+ descriptor.value = createAuthenticatedMethod(originalMethod, authProvider, authOptions);
43
65
  copyMetadata(originalMethod, descriptor.value);
44
66
  return descriptor;
45
67
  }
@@ -47,7 +69,7 @@ function Authenticated(authProvider) {
47
69
  };
48
70
  }
49
71
  __name(Authenticated, "Authenticated");
50
- function createAuthenticatedMethod(originalMethod, authProvider) {
72
+ function createAuthenticatedMethod(originalMethod, authProvider, options) {
51
73
  return async function(args, meta) {
52
74
  const token = meta?.authorization?.token;
53
75
  if (!token) {
@@ -64,9 +86,29 @@ function createAuthenticatedMethod(originalMethod, authProvider) {
64
86
  }
65
87
  throw new AuthenticationError(`Token verification failed: ${error instanceof Error ? error.message : String(error)}`, "VERIFICATION_FAILED");
66
88
  }
67
- return originalMethod.apply(this, [
68
- args
69
- ]);
89
+ if (options.getUser !== false) {
90
+ try {
91
+ const user = await authProvider.getUser(token);
92
+ return await authUserStorage.run(user, async () => {
93
+ return await originalMethod.apply(this, [
94
+ args
95
+ ]);
96
+ });
97
+ } catch (error) {
98
+ console.warn("Failed to fetch user information:", error);
99
+ return await authUserStorage.run(void 0, async () => {
100
+ return await originalMethod.apply(this, [
101
+ args
102
+ ]);
103
+ });
104
+ }
105
+ } else {
106
+ return await authUserStorage.run(void 0, async () => {
107
+ return await originalMethod.apply(this, [
108
+ args
109
+ ]);
110
+ });
111
+ }
70
112
  };
71
113
  }
72
114
  __name(createAuthenticatedMethod, "createAuthenticatedMethod");
@@ -123,18 +165,23 @@ var AuthProvider = class extends AuthProviderBase {
123
165
  const finalConfig = config || this.config;
124
166
  switch (this.providerType) {
125
167
  case "cognito": {
126
- const { AuthCognito } = await import("./cognito-VCVS77OX.mjs");
168
+ const { AuthCognito } = await import("./cognito-I6V5YNYM.mjs");
127
169
  this.providerInstance = new AuthCognito();
128
170
  await this.providerInstance.init(finalConfig);
129
171
  break;
130
172
  }
131
- // Add more providers here in the future
132
- // case 'clerk': {
133
- // const { AuthClerk } = await import('./providers/clerk');
134
- // this.providerInstance = new AuthClerk();
135
- // await this.providerInstance.init(finalConfig);
136
- // break;
137
- // }
173
+ case "auth0": {
174
+ const { AuthAuth0 } = await import("./auth0-54GZT2EI.mjs");
175
+ this.providerInstance = new AuthAuth0();
176
+ await this.providerInstance.init(finalConfig);
177
+ break;
178
+ }
179
+ case "clerk": {
180
+ const { AuthClerk } = await import("./clerk-FR7ITM33.mjs");
181
+ this.providerInstance = new AuthClerk();
182
+ await this.providerInstance.init(finalConfig);
183
+ break;
184
+ }
138
185
  default:
139
186
  throw new Error(`Unsupported auth provider: ${this.providerType}. Supported providers: cognito`);
140
187
  }
@@ -176,6 +223,7 @@ var AuthProvider = class extends AuthProviderBase {
176
223
 
177
224
  export {
178
225
  __name,
226
+ getAuthUser,
179
227
  AuthenticationError,
180
228
  Authenticated,
181
229
  isAuthenticationRequired,
@@ -0,0 +1,115 @@
1
+ import {
2
+ AuthProviderBase,
3
+ __name
4
+ } from "./chunk-EVD2TRPR.mjs";
5
+
6
+ // src/providers/clerk.ts
7
+ import axios from "axios";
8
+ import jwt from "jsonwebtoken";
9
+ import jwkToPem from "jwk-to-pem";
10
+ var AuthClerk = class extends AuthProviderBase {
11
+ static {
12
+ __name(this, "AuthClerk");
13
+ }
14
+ clerkFrontendApi = "";
15
+ clerkSecretKey = "";
16
+ clerkJWKSUrl = "";
17
+ clerkIssuer = "";
18
+ jwksCache = null;
19
+ mode = "session";
20
+ oauthTokenUrl = "";
21
+ clientId;
22
+ clientSecret;
23
+ redirectUri;
24
+ /**
25
+ * Initialize Clerk Auth Provider
26
+ */
27
+ async init(config) {
28
+ this.clerkFrontendApi = config?.frontendApi || process.env.CLERK_FRONTEND_API || "";
29
+ this.clerkSecretKey = config?.secretKey || process.env.CLERK_SECRET_KEY || "";
30
+ if (!this.clerkFrontendApi || !this.clerkSecretKey) {
31
+ throw new Error("Missing Clerk configuration: frontendApi and secretKey are required");
32
+ }
33
+ this.clerkIssuer = `https://${this.clerkFrontendApi}`;
34
+ this.clerkJWKSUrl = `${this.clerkIssuer}/.well-known/jwks.json`;
35
+ if (config?.clientId && config?.clientSecret && config?.redirectUri) {
36
+ this.mode = "oauth";
37
+ this.clientId = config.clientId;
38
+ this.clientSecret = config.clientSecret;
39
+ this.redirectUri = config.redirectUri;
40
+ this.oauthTokenUrl = `${this.clerkIssuer}/oauth/token`;
41
+ }
42
+ }
43
+ /**
44
+ * Refresh tokens (OAuth mode only)
45
+ */
46
+ async refreshToken(refreshToken) {
47
+ if (this.mode !== "oauth") {
48
+ throw new Error("Clerk is in Session Mode: refresh tokens are not supported. Enable OAuth mode.");
49
+ }
50
+ const payload = {
51
+ grant_type: "refresh_token",
52
+ refresh_token: refreshToken,
53
+ client_id: this.clientId,
54
+ client_secret: this.clientSecret,
55
+ redirect_uri: this.redirectUri
56
+ };
57
+ const { data } = await axios.post(this.oauthTokenUrl, payload, {
58
+ headers: {
59
+ "Content-Type": "application/json"
60
+ }
61
+ });
62
+ return data;
63
+ }
64
+ /**
65
+ * Verify JWT using JWKS
66
+ */
67
+ async verifyToken(token) {
68
+ await this.verifyJwt(token);
69
+ return true;
70
+ }
71
+ /**
72
+ * Extract user data from ID token
73
+ */
74
+ async getUser(idToken) {
75
+ const decoded = jwt.decode(idToken);
76
+ if (!decoded) throw new Error("Invalid ID token");
77
+ return {
78
+ sub: decoded.sub,
79
+ email: decoded.email,
80
+ email_verified: decoded.email_verified,
81
+ first_name: decoded.given_name,
82
+ last_name: decoded.family_name,
83
+ attributes: decoded
84
+ };
85
+ }
86
+ /**
87
+ * JWT verification using JWKS
88
+ */
89
+ async verifyJwt(token) {
90
+ const decoded = jwt.decode(token, {
91
+ complete: true
92
+ });
93
+ if (!decoded) throw new Error("Invalid token");
94
+ const jwks = await this.fetchJWKS();
95
+ const key = jwks.find((k) => k.kid === decoded.header.kid);
96
+ if (!key) throw new Error("Signing key not found in JWKS");
97
+ const pem = jwkToPem(key);
98
+ return jwt.verify(token, pem, {
99
+ algorithms: [
100
+ "RS256"
101
+ ],
102
+ issuer: this.clerkIssuer
103
+ });
104
+ }
105
+ async fetchJWKS() {
106
+ if (!this.jwksCache) {
107
+ const { data } = await axios.get(this.clerkJWKSUrl);
108
+ this.jwksCache = data.keys;
109
+ }
110
+ return this.jwksCache;
111
+ }
112
+ };
113
+ export {
114
+ AuthClerk
115
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  AuthProviderBase,
3
3
  __name
4
- } from "./chunk-YC7GFXAO.mjs";
4
+ } from "./chunk-EVD2TRPR.mjs";
5
5
 
6
6
  // src/providers/cognito.ts
7
7
  import { CognitoIdentityProviderClient, InitiateAuthCommand } from "@aws-sdk/client-cognito-identity-provider";
package/dist/index.d.mts CHANGED
@@ -1,3 +1,35 @@
1
+ /**
2
+ * Shared types for @leanmcp/auth
3
+ */
4
+ /**
5
+ * Options for the Authenticated decorator
6
+ */
7
+ interface AuthenticatedOptions {
8
+ /**
9
+ * Whether to fetch and attach user information to authUser variable
10
+ * @default true
11
+ */
12
+ getUser?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Global authUser type declaration
17
+ * This makes authUser available in @Authenticated methods without explicit declaration
18
+ */
19
+ declare global {
20
+ /**
21
+ * Authenticated user object automatically available in @Authenticated methods
22
+ *
23
+ * Implemented as a getter that reads from AsyncLocalStorage for concurrency safety.
24
+ * Each request has its own isolated context - 100% safe for concurrent requests.
25
+ */
26
+ const authUser: any;
27
+ }
28
+ /**
29
+ * Get the current authenticated user from the async context
30
+ * This is safe for concurrent requests as each request has its own context
31
+ */
32
+ declare function getAuthUser(): any;
1
33
  /**
2
34
  * Authentication error class for better error handling
3
35
  */
@@ -8,24 +40,40 @@ declare class AuthenticationError extends Error {
8
40
  /**
9
41
  * Decorator to protect MCP tools, prompts, resources, or entire services with authentication
10
42
  *
43
+ * CONCURRENCY SAFE: Uses AsyncLocalStorage to ensure each request has its own isolated
44
+ * authUser context, preventing race conditions in high-concurrency scenarios.
45
+ *
11
46
  * Usage:
12
47
  *
13
- * 1. Protect individual methods:
48
+ * 1. Protect individual methods with automatic user info:
14
49
  * ```typescript
15
50
  * @Tool({ description: 'Analyze sentiment' })
16
- * @Authenticated(authProvider)
51
+ * @Authenticated(authProvider, { getUser: true })
17
52
  * async analyzeSentiment(args: AnalyzeSentimentInput): Promise<AnalyzeSentimentOutput> {
18
- * // This method requires authentication
53
+ * // authUser is automatically available in method scope
54
+ * console.log('User:', authUser);
55
+ * console.log('User ID:', authUser.sub);
56
+ * }
57
+ * ```
58
+ *
59
+ * 2. Protect without fetching user info:
60
+ * ```typescript
61
+ * @Tool({ description: 'Public tool' })
62
+ * @Authenticated(authProvider, { getUser: false })
63
+ * async publicTool(args: PublicToolInput): Promise<PublicToolOutput> {
64
+ * // Only verifies token, doesn't fetch user info
19
65
  * }
20
66
  * ```
21
67
  *
22
- * 2. Protect entire service (all tools/prompts/resources):
68
+ * 3. Protect entire service (all tools/prompts/resources):
23
69
  * ```typescript
24
70
  * @Authenticated(authProvider)
25
71
  * export class SentimentAnalysisService {
26
72
  * @Tool({ description: 'Analyze sentiment' })
27
73
  * async analyzeSentiment(args: AnalyzeSentimentInput) {
28
74
  * // All methods in this service require authentication
75
+ * // authUser is automatically available in all methods
76
+ * console.log('User:', authUser);
29
77
  * }
30
78
  * }
31
79
  * ```
@@ -48,8 +96,9 @@ declare class AuthenticationError extends Error {
48
96
  * ```
49
97
  *
50
98
  * @param authProvider - Instance of AuthProviderBase to use for token verification
99
+ * @param options - Optional configuration for authentication behavior
51
100
  */
52
- declare function Authenticated(authProvider: AuthProviderBase): (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) => any;
101
+ declare function Authenticated(authProvider: AuthProviderBase, options?: AuthenticatedOptions): (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) => any;
53
102
  /**
54
103
  * Check if a method or class requires authentication
55
104
  */
@@ -118,4 +167,4 @@ declare class AuthProvider extends AuthProviderBase {
118
167
  getProviderType(): string;
119
168
  }
120
169
 
121
- export { AuthProvider, AuthProviderBase, Authenticated, AuthenticationError, getAuthProvider, isAuthenticationRequired };
170
+ export { AuthProvider, AuthProviderBase, Authenticated, type AuthenticatedOptions, AuthenticationError, getAuthProvider, getAuthUser, isAuthenticationRequired };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,35 @@
1
+ /**
2
+ * Shared types for @leanmcp/auth
3
+ */
4
+ /**
5
+ * Options for the Authenticated decorator
6
+ */
7
+ interface AuthenticatedOptions {
8
+ /**
9
+ * Whether to fetch and attach user information to authUser variable
10
+ * @default true
11
+ */
12
+ getUser?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Global authUser type declaration
17
+ * This makes authUser available in @Authenticated methods without explicit declaration
18
+ */
19
+ declare global {
20
+ /**
21
+ * Authenticated user object automatically available in @Authenticated methods
22
+ *
23
+ * Implemented as a getter that reads from AsyncLocalStorage for concurrency safety.
24
+ * Each request has its own isolated context - 100% safe for concurrent requests.
25
+ */
26
+ const authUser: any;
27
+ }
28
+ /**
29
+ * Get the current authenticated user from the async context
30
+ * This is safe for concurrent requests as each request has its own context
31
+ */
32
+ declare function getAuthUser(): any;
1
33
  /**
2
34
  * Authentication error class for better error handling
3
35
  */
@@ -8,24 +40,40 @@ declare class AuthenticationError extends Error {
8
40
  /**
9
41
  * Decorator to protect MCP tools, prompts, resources, or entire services with authentication
10
42
  *
43
+ * CONCURRENCY SAFE: Uses AsyncLocalStorage to ensure each request has its own isolated
44
+ * authUser context, preventing race conditions in high-concurrency scenarios.
45
+ *
11
46
  * Usage:
12
47
  *
13
- * 1. Protect individual methods:
48
+ * 1. Protect individual methods with automatic user info:
14
49
  * ```typescript
15
50
  * @Tool({ description: 'Analyze sentiment' })
16
- * @Authenticated(authProvider)
51
+ * @Authenticated(authProvider, { getUser: true })
17
52
  * async analyzeSentiment(args: AnalyzeSentimentInput): Promise<AnalyzeSentimentOutput> {
18
- * // This method requires authentication
53
+ * // authUser is automatically available in method scope
54
+ * console.log('User:', authUser);
55
+ * console.log('User ID:', authUser.sub);
56
+ * }
57
+ * ```
58
+ *
59
+ * 2. Protect without fetching user info:
60
+ * ```typescript
61
+ * @Tool({ description: 'Public tool' })
62
+ * @Authenticated(authProvider, { getUser: false })
63
+ * async publicTool(args: PublicToolInput): Promise<PublicToolOutput> {
64
+ * // Only verifies token, doesn't fetch user info
19
65
  * }
20
66
  * ```
21
67
  *
22
- * 2. Protect entire service (all tools/prompts/resources):
68
+ * 3. Protect entire service (all tools/prompts/resources):
23
69
  * ```typescript
24
70
  * @Authenticated(authProvider)
25
71
  * export class SentimentAnalysisService {
26
72
  * @Tool({ description: 'Analyze sentiment' })
27
73
  * async analyzeSentiment(args: AnalyzeSentimentInput) {
28
74
  * // All methods in this service require authentication
75
+ * // authUser is automatically available in all methods
76
+ * console.log('User:', authUser);
29
77
  * }
30
78
  * }
31
79
  * ```
@@ -48,8 +96,9 @@ declare class AuthenticationError extends Error {
48
96
  * ```
49
97
  *
50
98
  * @param authProvider - Instance of AuthProviderBase to use for token verification
99
+ * @param options - Optional configuration for authentication behavior
51
100
  */
52
- declare function Authenticated(authProvider: AuthProviderBase): (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) => any;
101
+ declare function Authenticated(authProvider: AuthProviderBase, options?: AuthenticatedOptions): (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) => any;
53
102
  /**
54
103
  * Check if a method or class requires authentication
55
104
  */
@@ -118,4 +167,4 @@ declare class AuthProvider extends AuthProviderBase {
118
167
  getProviderType(): string;
119
168
  }
120
169
 
121
- export { AuthProvider, AuthProviderBase, Authenticated, AuthenticationError, getAuthProvider, isAuthenticationRequired };
170
+ export { AuthProvider, AuthProviderBase, Authenticated, type AuthenticatedOptions, AuthenticationError, getAuthProvider, getAuthUser, isAuthenticationRequired };