@leanmcp/auth 0.2.0 → 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.
- package/README.md +269 -28
- package/dist/{auth0-A25F3NOH.mjs → auth0-54GZT2EI.mjs} +1 -1
- package/dist/{chunk-KKO5VLFX.mjs → chunk-EVD2TRPR.mjs} +53 -10
- package/dist/{clerk-XNMGPYSN.mjs → clerk-FR7ITM33.mjs} +1 -1
- package/dist/{cognito-2RRSLFKI.mjs → cognito-I6V5YNYM.mjs} +1 -1
- package/dist/index.d.mts +55 -6
- package/dist/index.d.ts +55 -6
- package/dist/index.js +52 -8
- package/dist/index.mjs +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,11 +5,12 @@ Authentication module for LeanMCP providing token-based authentication decorator
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **@Authenticated decorator** - Protect MCP tools, prompts, and resources with token authentication
|
|
8
|
+
- **Automatic authUser injection** - Access decoded user info via global `authUser` variable in protected methods
|
|
8
9
|
- **Multi-provider support** - AWS Cognito, Clerk, Auth0
|
|
9
10
|
- **Method or class-level protection** - Apply to individual methods or entire services
|
|
10
11
|
- **Automatic token validation** - Validates tokens before method execution
|
|
11
12
|
- **Custom error handling** - Detailed error codes for different auth failures
|
|
12
|
-
- **Type-safe** - Full TypeScript support with type inference
|
|
13
|
+
- **Type-safe** - Full TypeScript support with type inference and global type declarations
|
|
13
14
|
- **OAuth & Session modes** - Support for both session-based and OAuth refresh token flows
|
|
14
15
|
|
|
15
16
|
## Installation
|
|
@@ -59,13 +60,20 @@ import { Tool } from "@leanmcp/core";
|
|
|
59
60
|
import { Authenticated } from "@leanmcp/auth";
|
|
60
61
|
|
|
61
62
|
export class SentimentService {
|
|
62
|
-
// This method requires authentication
|
|
63
|
+
// This method requires authentication with automatic user info
|
|
63
64
|
@Tool({ description: 'Analyze sentiment (requires auth)' })
|
|
64
|
-
@Authenticated(authProvider)
|
|
65
|
+
@Authenticated(authProvider) // getUser: true by default
|
|
65
66
|
async analyzeSentiment(input: { text: string }) {
|
|
66
67
|
// Token is automatically validated from _meta.authorization.token
|
|
67
|
-
//
|
|
68
|
-
|
|
68
|
+
// authUser is automatically available with decoded JWT payload
|
|
69
|
+
console.log('User ID:', authUser.sub);
|
|
70
|
+
console.log('Email:', authUser.email);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
sentiment: 'positive',
|
|
74
|
+
score: 0.8,
|
|
75
|
+
analyzedBy: authUser.sub
|
|
76
|
+
};
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
// This method is public
|
|
@@ -86,16 +94,134 @@ import { Authenticated } from "@leanmcp/auth";
|
|
|
86
94
|
export class SecureService {
|
|
87
95
|
@Tool({ description: 'Protected tool 1' })
|
|
88
96
|
async tool1(input: { data: string }) {
|
|
89
|
-
//
|
|
97
|
+
// authUser is automatically available in all methods
|
|
98
|
+
console.log('Authenticated user:', authUser.email);
|
|
99
|
+
return { data: input.data, userId: authUser.sub };
|
|
90
100
|
}
|
|
91
101
|
|
|
92
102
|
@Tool({ description: 'Protected tool 2' })
|
|
93
103
|
async tool2(input: { data: string }) {
|
|
94
|
-
//
|
|
104
|
+
// authUser is available here too
|
|
105
|
+
return { data: input.data, userId: authUser.sub };
|
|
95
106
|
}
|
|
96
107
|
}
|
|
97
108
|
```
|
|
98
109
|
|
|
110
|
+
## authUser Variable
|
|
111
|
+
|
|
112
|
+
### Automatic User Information Injection
|
|
113
|
+
|
|
114
|
+
When you use the `@Authenticated` decorator, a global `authUser` variable is automatically injected into your protected methods containing the decoded JWT token payload.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
@Tool({ description: 'Create post' })
|
|
118
|
+
@Authenticated(authProvider)
|
|
119
|
+
async createPost(input: { title: string, content: string }) {
|
|
120
|
+
// authUser is automatically available - no need to pass it as a parameter
|
|
121
|
+
console.log('User ID:', authUser.sub);
|
|
122
|
+
console.log('Email:', authUser.email);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
id: generateId(),
|
|
126
|
+
title: input.title,
|
|
127
|
+
content: input.content,
|
|
128
|
+
authorId: authUser.sub,
|
|
129
|
+
authorEmail: authUser.email
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Controlling User Data Fetching
|
|
135
|
+
|
|
136
|
+
You can control whether user information is fetched using the `getUser` option:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// Fetch user info (default behavior)
|
|
140
|
+
@Authenticated(authProvider, { getUser: true })
|
|
141
|
+
async methodWithUserInfo(input: any) {
|
|
142
|
+
// authUser is available
|
|
143
|
+
console.log(authUser);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Only verify token, don't fetch user info
|
|
147
|
+
@Authenticated(authProvider, { getUser: false })
|
|
148
|
+
async methodWithoutUserInfo(input: any) {
|
|
149
|
+
// authUser is undefined
|
|
150
|
+
// Faster execution, use when you only need token validation
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Provider-Specific User Data
|
|
155
|
+
|
|
156
|
+
The structure of `authUser` depends on your authentication provider:
|
|
157
|
+
|
|
158
|
+
**AWS Cognito:**
|
|
159
|
+
```typescript
|
|
160
|
+
{
|
|
161
|
+
sub: 'user-uuid',
|
|
162
|
+
email: 'user@example.com',
|
|
163
|
+
email_verified: true,
|
|
164
|
+
'cognito:username': 'username',
|
|
165
|
+
'cognito:groups': ['admin', 'users'],
|
|
166
|
+
// ... other Cognito claims
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Clerk:**
|
|
171
|
+
```typescript
|
|
172
|
+
{
|
|
173
|
+
sub: 'user_2abc123xyz',
|
|
174
|
+
userId: 'user_2abc123xyz',
|
|
175
|
+
email: 'user@example.com',
|
|
176
|
+
firstName: 'John',
|
|
177
|
+
lastName: 'Doe',
|
|
178
|
+
imageUrl: 'https://img.clerk.com/...',
|
|
179
|
+
// ... other Clerk claims
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Auth0:**
|
|
184
|
+
```typescript
|
|
185
|
+
{
|
|
186
|
+
sub: 'auth0|507f1f77bcf86cd799439011',
|
|
187
|
+
email: 'user@example.com',
|
|
188
|
+
email_verified: true,
|
|
189
|
+
name: 'John Doe',
|
|
190
|
+
picture: 'https://s.gravatar.com/avatar/...',
|
|
191
|
+
// ... other Auth0 claims
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### TypeScript Support
|
|
196
|
+
|
|
197
|
+
The `authUser` variable is globally declared and available without TypeScript errors:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// No need to import or declare authUser
|
|
201
|
+
@Authenticated(authProvider)
|
|
202
|
+
async myMethod(input: any) {
|
|
203
|
+
// TypeScript knows about authUser
|
|
204
|
+
const userId: string = authUser.sub;
|
|
205
|
+
const email: string = authUser.email;
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
For better type safety, you can create a typed interface:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
interface MyAuthUser {
|
|
213
|
+
sub: string;
|
|
214
|
+
email: string;
|
|
215
|
+
name?: string;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@Authenticated(authProvider)
|
|
219
|
+
async myMethod(input: any) {
|
|
220
|
+
const user = authUser as MyAuthUser;
|
|
221
|
+
console.log(user.email); // Fully typed
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
99
225
|
## Usage
|
|
100
226
|
|
|
101
227
|
### Client Side - Calling Protected Methods
|
|
@@ -345,13 +471,24 @@ class AuthProvider {
|
|
|
345
471
|
### @Authenticated Decorator
|
|
346
472
|
|
|
347
473
|
```typescript
|
|
348
|
-
function Authenticated(
|
|
474
|
+
function Authenticated(
|
|
475
|
+
authProvider: AuthProvider,
|
|
476
|
+
options?: AuthenticatedOptions
|
|
477
|
+
): ClassDecorator | MethodDecorator;
|
|
478
|
+
|
|
479
|
+
interface AuthenticatedOptions {
|
|
480
|
+
getUser?: boolean; // Default: true
|
|
481
|
+
}
|
|
349
482
|
```
|
|
350
483
|
|
|
351
484
|
Can be applied to:
|
|
352
485
|
- **Classes** - Protects all methods in the class
|
|
353
486
|
- **Methods** - Protects individual methods
|
|
354
487
|
|
|
488
|
+
**Options:**
|
|
489
|
+
- `getUser: true` (default) - Fetches user info and injects `authUser` variable
|
|
490
|
+
- `getUser: false` - Only validates token, skips user info fetch (faster)
|
|
491
|
+
|
|
355
492
|
### AuthenticationError
|
|
356
493
|
|
|
357
494
|
```typescript
|
|
@@ -415,9 +552,17 @@ await authProvider.init();
|
|
|
415
552
|
// Create service with protected methods
|
|
416
553
|
@Authenticated(authProvider)
|
|
417
554
|
class MyService {
|
|
418
|
-
@Tool()
|
|
555
|
+
@Tool({ description: 'Process protected data' })
|
|
419
556
|
async protectedTool(input: { data: string }) {
|
|
420
|
-
|
|
557
|
+
// authUser is automatically available
|
|
558
|
+
console.log('User:', authUser.sub);
|
|
559
|
+
console.log('Email:', authUser.email);
|
|
560
|
+
|
|
561
|
+
return {
|
|
562
|
+
result: "Protected data",
|
|
563
|
+
processedBy: authUser.sub,
|
|
564
|
+
userEmail: authUser.email
|
|
565
|
+
};
|
|
421
566
|
}
|
|
422
567
|
}
|
|
423
568
|
|
|
@@ -457,9 +602,27 @@ await authProviderOAuth.init();
|
|
|
457
602
|
@Authenticated(authProvider)
|
|
458
603
|
class UserService {
|
|
459
604
|
@Tool({ description: 'Get user profile' })
|
|
460
|
-
async getProfile(
|
|
461
|
-
//
|
|
462
|
-
return {
|
|
605
|
+
async getProfile() {
|
|
606
|
+
// authUser is automatically available with Clerk user data
|
|
607
|
+
return {
|
|
608
|
+
userId: authUser.userId || authUser.sub,
|
|
609
|
+
email: authUser.email,
|
|
610
|
+
firstName: authUser.firstName,
|
|
611
|
+
lastName: authUser.lastName,
|
|
612
|
+
imageUrl: authUser.imageUrl
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
@Tool({ description: 'Create user post' })
|
|
617
|
+
async createPost(input: { title: string, content: string }) {
|
|
618
|
+
// Access authUser in any protected method
|
|
619
|
+
return {
|
|
620
|
+
id: generateId(),
|
|
621
|
+
title: input.title,
|
|
622
|
+
content: input.content,
|
|
623
|
+
authorId: authUser.userId,
|
|
624
|
+
authorEmail: authUser.email
|
|
625
|
+
};
|
|
463
626
|
}
|
|
464
627
|
}
|
|
465
628
|
|
|
@@ -492,13 +655,27 @@ await authProvider.init();
|
|
|
492
655
|
class SecureAPIService {
|
|
493
656
|
@Tool({ description: 'Get sensitive data' })
|
|
494
657
|
async getSensitiveData(input: { dataId: string }) {
|
|
495
|
-
//
|
|
496
|
-
|
|
658
|
+
// authUser is automatically available with Auth0 user data
|
|
659
|
+
console.log('User:', authUser.sub);
|
|
660
|
+
console.log('Email:', authUser.email);
|
|
661
|
+
|
|
662
|
+
return {
|
|
663
|
+
dataId: input.dataId,
|
|
664
|
+
data: "Sensitive information",
|
|
665
|
+
accessedBy: authUser.sub,
|
|
666
|
+
userName: authUser.name
|
|
667
|
+
};
|
|
497
668
|
}
|
|
498
669
|
|
|
499
670
|
@Tool({ description: 'Update user settings' })
|
|
500
671
|
async updateSettings(input: { settings: Record<string, any> }) {
|
|
501
|
-
|
|
672
|
+
// Access authUser in any protected method
|
|
673
|
+
return {
|
|
674
|
+
success: true,
|
|
675
|
+
settings: input.settings,
|
|
676
|
+
userId: authUser.sub,
|
|
677
|
+
updatedBy: authUser.email
|
|
678
|
+
};
|
|
502
679
|
}
|
|
503
680
|
}
|
|
504
681
|
|
|
@@ -533,17 +710,27 @@ await auth0Auth.init();
|
|
|
533
710
|
// Use different providers for different services
|
|
534
711
|
@Authenticated(clerkAuth)
|
|
535
712
|
class UserService {
|
|
536
|
-
@Tool()
|
|
537
|
-
async getUserData(
|
|
538
|
-
|
|
713
|
+
@Tool({ description: 'Get user data' })
|
|
714
|
+
async getUserData() {
|
|
715
|
+
// authUser contains Clerk user data
|
|
716
|
+
return {
|
|
717
|
+
userId: authUser.userId,
|
|
718
|
+
email: authUser.email,
|
|
719
|
+
firstName: authUser.firstName
|
|
720
|
+
};
|
|
539
721
|
}
|
|
540
722
|
}
|
|
541
723
|
|
|
542
724
|
@Authenticated(auth0Auth)
|
|
543
725
|
class AdminService {
|
|
544
|
-
@Tool()
|
|
545
|
-
async getAdminData(
|
|
546
|
-
|
|
726
|
+
@Tool({ description: 'Get admin data' })
|
|
727
|
+
async getAdminData() {
|
|
728
|
+
// authUser contains Auth0 user data
|
|
729
|
+
return {
|
|
730
|
+
adminId: authUser.sub,
|
|
731
|
+
email: authUser.email,
|
|
732
|
+
name: authUser.name
|
|
733
|
+
};
|
|
547
734
|
}
|
|
548
735
|
}
|
|
549
736
|
```
|
|
@@ -554,14 +741,19 @@ class AdminService {
|
|
|
554
741
|
2. **Decorator intercepts** the method call before execution
|
|
555
742
|
3. **Token is extracted** from `_meta.authorization.token`
|
|
556
743
|
4. **Token is validated** using the configured auth provider
|
|
557
|
-
5. **
|
|
558
|
-
6. **
|
|
744
|
+
5. **User info is fetched** (if `getUser: true`) and decoded from JWT
|
|
745
|
+
6. **authUser is injected** as a global variable in method scope
|
|
746
|
+
7. **Method executes** with clean business arguments and access to `authUser`
|
|
747
|
+
8. **authUser is cleaned up** after method execution
|
|
748
|
+
9. **Response returns** to client
|
|
559
749
|
|
|
560
750
|
**Key Benefits:**
|
|
561
751
|
- **Clean separation** - Authentication metadata separate from business data
|
|
562
752
|
- **MCP compliant** - Follows standard `_meta` pattern
|
|
563
753
|
- **Type-safe** - Input classes don't need token fields
|
|
754
|
+
- **Automatic user injection** - Access user data via `authUser` without manual extraction
|
|
564
755
|
- **Reusable** - Same input classes work for authenticated and public methods
|
|
756
|
+
- **Secure** - `authUser` is scoped to method execution and cleaned up after
|
|
565
757
|
|
|
566
758
|
## Best Practices
|
|
567
759
|
|
|
@@ -575,6 +767,9 @@ class AdminService {
|
|
|
575
767
|
8. **Choose the right mode** - Use Session mode for simpler setups, OAuth mode for refresh tokens
|
|
576
768
|
9. **Test token expiration** - Ensure your app handles expired tokens gracefully
|
|
577
769
|
10. **Monitor JWKS cache** - Providers cache JWKS keys for performance
|
|
770
|
+
11. **Use authUser for user context** - Access user data via `authUser` instead of parsing tokens manually
|
|
771
|
+
12. **Type authUser when needed** - Cast `authUser` to a typed interface for better type safety
|
|
772
|
+
13. **Use getUser: false for performance** - Skip user fetch when you only need token validation
|
|
578
773
|
|
|
579
774
|
## Quick Reference
|
|
580
775
|
|
|
@@ -634,15 +829,28 @@ const type = authProvider.getProviderType(); // 'cognito' | 'clerk' | 'auth0'
|
|
|
634
829
|
### Decorator Usage
|
|
635
830
|
|
|
636
831
|
```typescript
|
|
637
|
-
// Protect single method
|
|
832
|
+
// Protect single method with authUser (default)
|
|
638
833
|
@Authenticated(authProvider)
|
|
639
|
-
async myMethod(input: { data: string }) {
|
|
834
|
+
async myMethod(input: { data: string }) {
|
|
835
|
+
console.log(authUser.sub); // User ID available
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Protect method without fetching user (faster)
|
|
839
|
+
@Authenticated(authProvider, { getUser: false })
|
|
840
|
+
async fastMethod(input: { data: string }) {
|
|
841
|
+
// Only token validation, no authUser
|
|
842
|
+
}
|
|
640
843
|
|
|
641
844
|
// Protect entire class
|
|
642
845
|
@Authenticated(authProvider)
|
|
643
846
|
class MyService {
|
|
644
|
-
@Tool() async method1() {
|
|
645
|
-
|
|
847
|
+
@Tool() async method1() {
|
|
848
|
+
// authUser available in all methods
|
|
849
|
+
return { userId: authUser.sub };
|
|
850
|
+
}
|
|
851
|
+
@Tool() async method2() {
|
|
852
|
+
return { email: authUser.email };
|
|
853
|
+
}
|
|
646
854
|
}
|
|
647
855
|
|
|
648
856
|
// Check if authentication required
|
|
@@ -652,6 +860,39 @@ const required = isAuthenticationRequired(target, 'methodName');
|
|
|
652
860
|
const provider = getAuthProvider(target, 'methodName');
|
|
653
861
|
```
|
|
654
862
|
|
|
863
|
+
### authUser Quick Reference
|
|
864
|
+
|
|
865
|
+
```typescript
|
|
866
|
+
// Access user data in protected methods
|
|
867
|
+
@Authenticated(authProvider)
|
|
868
|
+
async createPost(input: { title: string }) {
|
|
869
|
+
// authUser is automatically available
|
|
870
|
+
const userId = authUser.sub; // User ID (all providers)
|
|
871
|
+
const email = authUser.email; // Email (all providers)
|
|
872
|
+
|
|
873
|
+
// Provider-specific fields
|
|
874
|
+
const clerkId = authUser.userId; // Clerk only
|
|
875
|
+
const firstName = authUser.firstName; // Clerk only
|
|
876
|
+
const cognitoGroups = authUser['cognito:groups']; // Cognito only
|
|
877
|
+
const auth0Name = authUser.name; // Auth0 only
|
|
878
|
+
|
|
879
|
+
return { authorId: userId, authorEmail: email };
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Type authUser for better type safety
|
|
883
|
+
interface MyUser {
|
|
884
|
+
sub: string;
|
|
885
|
+
email: string;
|
|
886
|
+
name?: string;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
@Authenticated(authProvider)
|
|
890
|
+
async typedMethod(input: any) {
|
|
891
|
+
const user = authUser as MyUser;
|
|
892
|
+
console.log(user.email); // Fully typed
|
|
893
|
+
}
|
|
894
|
+
```
|
|
895
|
+
|
|
655
896
|
## License
|
|
656
897
|
|
|
657
898
|
MIT
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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,19 +165,19 @@ 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-
|
|
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
173
|
case "auth0": {
|
|
132
|
-
const { AuthAuth0 } = await import("./auth0-
|
|
174
|
+
const { AuthAuth0 } = await import("./auth0-54GZT2EI.mjs");
|
|
133
175
|
this.providerInstance = new AuthAuth0();
|
|
134
176
|
await this.providerInstance.init(finalConfig);
|
|
135
177
|
break;
|
|
136
178
|
}
|
|
137
179
|
case "clerk": {
|
|
138
|
-
const { AuthClerk } = await import("./clerk-
|
|
180
|
+
const { AuthClerk } = await import("./clerk-FR7ITM33.mjs");
|
|
139
181
|
this.providerInstance = new AuthClerk();
|
|
140
182
|
await this.providerInstance.init(finalConfig);
|
|
141
183
|
break;
|
|
@@ -181,6 +223,7 @@ var AuthProvider = class extends AuthProviderBase {
|
|
|
181
223
|
|
|
182
224
|
export {
|
|
183
225
|
__name,
|
|
226
|
+
getAuthUser,
|
|
184
227
|
AuthenticationError,
|
|
185
228
|
Authenticated,
|
|
186
229
|
isAuthenticationRequired,
|
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
|
-
* //
|
|
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
|
-
*
|
|
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
|
-
* //
|
|
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
|
-
*
|
|
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.js
CHANGED
|
@@ -32,11 +32,19 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
32
32
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
33
|
|
|
34
34
|
// src/decorators.ts
|
|
35
|
-
function
|
|
35
|
+
function getAuthUser() {
|
|
36
|
+
return authUserStorage.getStore();
|
|
37
|
+
}
|
|
38
|
+
function Authenticated(authProvider, options) {
|
|
39
|
+
const authOptions = {
|
|
40
|
+
getUser: true,
|
|
41
|
+
...options
|
|
42
|
+
};
|
|
36
43
|
return function(target, propertyKey, descriptor) {
|
|
37
44
|
if (!propertyKey && !descriptor) {
|
|
38
45
|
Reflect.defineMetadata("auth:provider", authProvider, target);
|
|
39
46
|
Reflect.defineMetadata("auth:required", true, target);
|
|
47
|
+
Reflect.defineMetadata("auth:options", authOptions, target);
|
|
40
48
|
const prototype = target.prototype;
|
|
41
49
|
const methodNames = Object.getOwnPropertyNames(prototype).filter((name) => name !== "constructor" && typeof prototype[name] === "function");
|
|
42
50
|
for (const methodName of methodNames) {
|
|
@@ -45,7 +53,8 @@ function Authenticated(authProvider) {
|
|
|
45
53
|
const originalMethod = originalDescriptor.value;
|
|
46
54
|
Reflect.defineMetadata("auth:provider", authProvider, originalMethod);
|
|
47
55
|
Reflect.defineMetadata("auth:required", true, originalMethod);
|
|
48
|
-
|
|
56
|
+
Reflect.defineMetadata("auth:options", authOptions, originalMethod);
|
|
57
|
+
prototype[methodName] = createAuthenticatedMethod(originalMethod, authProvider, authOptions);
|
|
49
58
|
copyMetadata(originalMethod, prototype[methodName]);
|
|
50
59
|
}
|
|
51
60
|
}
|
|
@@ -55,14 +64,15 @@ function Authenticated(authProvider) {
|
|
|
55
64
|
const originalMethod = descriptor.value;
|
|
56
65
|
Reflect.defineMetadata("auth:provider", authProvider, originalMethod);
|
|
57
66
|
Reflect.defineMetadata("auth:required", true, originalMethod);
|
|
58
|
-
|
|
67
|
+
Reflect.defineMetadata("auth:options", authOptions, originalMethod);
|
|
68
|
+
descriptor.value = createAuthenticatedMethod(originalMethod, authProvider, authOptions);
|
|
59
69
|
copyMetadata(originalMethod, descriptor.value);
|
|
60
70
|
return descriptor;
|
|
61
71
|
}
|
|
62
72
|
throw new Error("@Authenticated can only be applied to classes or methods");
|
|
63
73
|
};
|
|
64
74
|
}
|
|
65
|
-
function createAuthenticatedMethod(originalMethod, authProvider) {
|
|
75
|
+
function createAuthenticatedMethod(originalMethod, authProvider, options) {
|
|
66
76
|
return async function(args, meta) {
|
|
67
77
|
const token = meta?.authorization?.token;
|
|
68
78
|
if (!token) {
|
|
@@ -79,9 +89,29 @@ function createAuthenticatedMethod(originalMethod, authProvider) {
|
|
|
79
89
|
}
|
|
80
90
|
throw new AuthenticationError(`Token verification failed: ${error instanceof Error ? error.message : String(error)}`, "VERIFICATION_FAILED");
|
|
81
91
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
92
|
+
if (options.getUser !== false) {
|
|
93
|
+
try {
|
|
94
|
+
const user = await authProvider.getUser(token);
|
|
95
|
+
return await authUserStorage.run(user, async () => {
|
|
96
|
+
return await originalMethod.apply(this, [
|
|
97
|
+
args
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.warn("Failed to fetch user information:", error);
|
|
102
|
+
return await authUserStorage.run(void 0, async () => {
|
|
103
|
+
return await originalMethod.apply(this, [
|
|
104
|
+
args
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
return await authUserStorage.run(void 0, async () => {
|
|
110
|
+
return await originalMethod.apply(this, [
|
|
111
|
+
args
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
85
115
|
};
|
|
86
116
|
}
|
|
87
117
|
function copyMetadata(source, target) {
|
|
@@ -108,11 +138,23 @@ function isAuthenticationRequired(target) {
|
|
|
108
138
|
function getAuthProvider(target) {
|
|
109
139
|
return Reflect.getMetadata("auth:provider", target);
|
|
110
140
|
}
|
|
111
|
-
var import_reflect_metadata, AuthenticationError;
|
|
141
|
+
var import_reflect_metadata, import_async_hooks, authUserStorage, AuthenticationError;
|
|
112
142
|
var init_decorators = __esm({
|
|
113
143
|
"src/decorators.ts"() {
|
|
114
144
|
"use strict";
|
|
115
145
|
import_reflect_metadata = require("reflect-metadata");
|
|
146
|
+
import_async_hooks = require("async_hooks");
|
|
147
|
+
authUserStorage = new import_async_hooks.AsyncLocalStorage();
|
|
148
|
+
__name(getAuthUser, "getAuthUser");
|
|
149
|
+
if (typeof globalThis !== "undefined" && !Object.getOwnPropertyDescriptor(globalThis, "authUser")) {
|
|
150
|
+
Object.defineProperty(globalThis, "authUser", {
|
|
151
|
+
get() {
|
|
152
|
+
return authUserStorage.getStore();
|
|
153
|
+
},
|
|
154
|
+
configurable: true,
|
|
155
|
+
enumerable: false
|
|
156
|
+
});
|
|
157
|
+
}
|
|
116
158
|
AuthenticationError = class extends Error {
|
|
117
159
|
static {
|
|
118
160
|
__name(this, "AuthenticationError");
|
|
@@ -513,6 +555,7 @@ __export(index_exports, {
|
|
|
513
555
|
Authenticated: () => Authenticated,
|
|
514
556
|
AuthenticationError: () => AuthenticationError,
|
|
515
557
|
getAuthProvider: () => getAuthProvider,
|
|
558
|
+
getAuthUser: () => getAuthUser,
|
|
516
559
|
isAuthenticationRequired: () => isAuthenticationRequired
|
|
517
560
|
});
|
|
518
561
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -610,5 +653,6 @@ init_index();
|
|
|
610
653
|
Authenticated,
|
|
611
654
|
AuthenticationError,
|
|
612
655
|
getAuthProvider,
|
|
656
|
+
getAuthUser,
|
|
613
657
|
isAuthenticationRequired
|
|
614
658
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -4,13 +4,15 @@ import {
|
|
|
4
4
|
Authenticated,
|
|
5
5
|
AuthenticationError,
|
|
6
6
|
getAuthProvider,
|
|
7
|
+
getAuthUser,
|
|
7
8
|
isAuthenticationRequired
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-EVD2TRPR.mjs";
|
|
9
10
|
export {
|
|
10
11
|
AuthProvider,
|
|
11
12
|
AuthProviderBase,
|
|
12
13
|
Authenticated,
|
|
13
14
|
AuthenticationError,
|
|
14
15
|
getAuthProvider,
|
|
16
|
+
getAuthUser,
|
|
15
17
|
isAuthenticationRequired
|
|
16
18
|
};
|