@mcp-z/oauth 1.0.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/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/cjs/account-utils.d.cts +107 -0
- package/dist/cjs/account-utils.d.ts +107 -0
- package/dist/cjs/account-utils.js +481 -0
- package/dist/cjs/account-utils.js.map +1 -0
- package/dist/cjs/index.d.cts +19 -0
- package/dist/cjs/index.d.ts +19 -0
- package/dist/cjs/index.js +149 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/jwt-auth.d.cts +53 -0
- package/dist/cjs/jwt-auth.d.ts +53 -0
- package/dist/cjs/jwt-auth.js +417 -0
- package/dist/cjs/jwt-auth.js.map +1 -0
- package/dist/cjs/key-utils.d.cts +131 -0
- package/dist/cjs/key-utils.d.ts +131 -0
- package/dist/cjs/key-utils.js +421 -0
- package/dist/cjs/key-utils.js.map +1 -0
- package/dist/cjs/lib/account-server/index.d.cts +45 -0
- package/dist/cjs/lib/account-server/index.d.ts +45 -0
- package/dist/cjs/lib/account-server/index.js +67 -0
- package/dist/cjs/lib/account-server/index.js.map +1 -0
- package/dist/cjs/lib/account-server/loopback.d.cts +22 -0
- package/dist/cjs/lib/account-server/loopback.d.ts +22 -0
- package/dist/cjs/lib/account-server/loopback.js +778 -0
- package/dist/cjs/lib/account-server/loopback.js.map +1 -0
- package/dist/cjs/lib/account-server/me.d.cts +23 -0
- package/dist/cjs/lib/account-server/me.d.ts +23 -0
- package/dist/cjs/lib/account-server/me.js +412 -0
- package/dist/cjs/lib/account-server/me.js.map +1 -0
- package/dist/cjs/lib/account-server/shared-utils.d.cts +6 -0
- package/dist/cjs/lib/account-server/shared-utils.d.ts +6 -0
- package/dist/cjs/lib/account-server/shared-utils.js +235 -0
- package/dist/cjs/lib/account-server/shared-utils.js.map +1 -0
- package/dist/cjs/lib/account-server/stateless.d.cts +20 -0
- package/dist/cjs/lib/account-server/stateless.d.ts +20 -0
- package/dist/cjs/lib/account-server/stateless.js +32 -0
- package/dist/cjs/lib/account-server/stateless.js.map +1 -0
- package/dist/cjs/lib/account-server/types.d.cts +32 -0
- package/dist/cjs/lib/account-server/types.d.ts +32 -0
- package/dist/cjs/lib/account-server/types.js +7 -0
- package/dist/cjs/lib/account-server/types.js.map +1 -0
- package/dist/cjs/lib/dcr-types.d.cts +126 -0
- package/dist/cjs/lib/dcr-types.d.ts +126 -0
- package/dist/cjs/lib/dcr-types.js +12 -0
- package/dist/cjs/lib/dcr-types.js.map +1 -0
- package/dist/cjs/lib/rfc-metadata-types.d.cts +46 -0
- package/dist/cjs/lib/rfc-metadata-types.d.ts +46 -0
- package/dist/cjs/lib/rfc-metadata-types.js +8 -0
- package/dist/cjs/lib/rfc-metadata-types.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/pkce.d.cts +36 -0
- package/dist/cjs/pkce.d.ts +36 -0
- package/dist/cjs/pkce.js +25 -0
- package/dist/cjs/pkce.js.map +1 -0
- package/dist/cjs/sanitizer.d.cts +37 -0
- package/dist/cjs/sanitizer.d.ts +37 -0
- package/dist/cjs/sanitizer.js +407 -0
- package/dist/cjs/sanitizer.js.map +1 -0
- package/dist/cjs/schemas/index.d.cts +36 -0
- package/dist/cjs/schemas/index.d.ts +36 -0
- package/dist/cjs/schemas/index.js +28 -0
- package/dist/cjs/schemas/index.js.map +1 -0
- package/dist/cjs/session-auth.d.cts +79 -0
- package/dist/cjs/session-auth.d.ts +79 -0
- package/dist/cjs/session-auth.js +354 -0
- package/dist/cjs/session-auth.js.map +1 -0
- package/dist/cjs/templates.d.cts +18 -0
- package/dist/cjs/templates.d.ts +18 -0
- package/dist/cjs/templates.js +38 -0
- package/dist/cjs/templates.js.map +1 -0
- package/dist/cjs/types.d.cts +343 -0
- package/dist/cjs/types.d.ts +343 -0
- package/dist/cjs/types.js +210 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/account-utils.d.ts +107 -0
- package/dist/esm/account-utils.js +179 -0
- package/dist/esm/account-utils.js.map +1 -0
- package/dist/esm/index.d.ts +19 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/jwt-auth.d.ts +53 -0
- package/dist/esm/jwt-auth.js +164 -0
- package/dist/esm/jwt-auth.js.map +1 -0
- package/dist/esm/key-utils.d.ts +131 -0
- package/dist/esm/key-utils.js +143 -0
- package/dist/esm/key-utils.js.map +1 -0
- package/dist/esm/lib/account-server/index.d.ts +45 -0
- package/dist/esm/lib/account-server/index.js +41 -0
- package/dist/esm/lib/account-server/index.js.map +1 -0
- package/dist/esm/lib/account-server/loopback.d.ts +22 -0
- package/dist/esm/lib/account-server/loopback.js +372 -0
- package/dist/esm/lib/account-server/loopback.js.map +1 -0
- package/dist/esm/lib/account-server/me.d.ts +23 -0
- package/dist/esm/lib/account-server/me.js +170 -0
- package/dist/esm/lib/account-server/me.js.map +1 -0
- package/dist/esm/lib/account-server/shared-utils.d.ts +6 -0
- package/dist/esm/lib/account-server/shared-utils.js +24 -0
- package/dist/esm/lib/account-server/shared-utils.js.map +1 -0
- package/dist/esm/lib/account-server/stateless.d.ts +20 -0
- package/dist/esm/lib/account-server/stateless.js +25 -0
- package/dist/esm/lib/account-server/stateless.js.map +1 -0
- package/dist/esm/lib/account-server/types.d.ts +32 -0
- package/dist/esm/lib/account-server/types.js +6 -0
- package/dist/esm/lib/account-server/types.js.map +1 -0
- package/dist/esm/lib/dcr-types.d.ts +126 -0
- package/dist/esm/lib/dcr-types.js +13 -0
- package/dist/esm/lib/dcr-types.js.map +1 -0
- package/dist/esm/lib/rfc-metadata-types.d.ts +46 -0
- package/dist/esm/lib/rfc-metadata-types.js +7 -0
- package/dist/esm/lib/rfc-metadata-types.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/pkce.d.ts +36 -0
- package/dist/esm/pkce.js +33 -0
- package/dist/esm/pkce.js.map +1 -0
- package/dist/esm/sanitizer.d.ts +37 -0
- package/dist/esm/sanitizer.js +256 -0
- package/dist/esm/sanitizer.js.map +1 -0
- package/dist/esm/schemas/index.d.ts +36 -0
- package/dist/esm/schemas/index.js +19 -0
- package/dist/esm/schemas/index.js.map +1 -0
- package/dist/esm/session-auth.d.ts +79 -0
- package/dist/esm/session-auth.js +141 -0
- package/dist/esm/session-auth.js.map +1 -0
- package/dist/esm/templates.d.ts +18 -0
- package/dist/esm/templates.js +132 -0
- package/dist/esm/templates.js.map +1 -0
- package/dist/esm/types.d.ts +343 -0
- package/dist/esm/types.js +34 -0
- package/dist/esm/types.js.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT-based user authentication for multi-tenant deployments
|
|
3
|
+
*
|
|
4
|
+
* Extracts user ID from JWT tokens with signature and claims verification.
|
|
5
|
+
* Supports HS256, RS256, ES256 algorithms via JOSE library.
|
|
6
|
+
*/ import { createRemoteJWKSet, importSPKI, jwtVerify } from 'jose';
|
|
7
|
+
/**
|
|
8
|
+
* JWT-based user authentication provider
|
|
9
|
+
*
|
|
10
|
+
* Verifies JWT tokens and extracts user IDs from claims.
|
|
11
|
+
* Use for multi-tenant deployments where users authenticate via JWT.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // HS256 with shared secret
|
|
16
|
+
* const userAuth = new JWTUserAuth({
|
|
17
|
+
* secret: process.env.JWT_SECRET!,
|
|
18
|
+
* issuer: 'https://auth.example.com',
|
|
19
|
+
* audience: 'api.example.com',
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // RS256 with public key
|
|
23
|
+
* const userAuth = new JWTUserAuth({
|
|
24
|
+
* publicKey: process.env.JWT_PUBLIC_KEY!,
|
|
25
|
+
* issuer: 'https://auth.example.com',
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // RS256 with JWKS URL (dynamic key rotation)
|
|
29
|
+
* const userAuth = new JWTUserAuth({
|
|
30
|
+
* jwksUrl: 'https://auth.example.com/.well-known/jwks.json',
|
|
31
|
+
* issuer: 'https://auth.example.com',
|
|
32
|
+
* audience: 'api.example.com',
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/ export class JWTUserAuth {
|
|
36
|
+
/**
|
|
37
|
+
* Extract and verify user ID from JWT token
|
|
38
|
+
*
|
|
39
|
+
* @param req - HTTP request object with Authorization header
|
|
40
|
+
* @returns User ID from verified JWT claims
|
|
41
|
+
* @throws Error if token missing, invalid, expired, or claims invalid
|
|
42
|
+
*/ async getUserId(req) {
|
|
43
|
+
var _httpReq_headers;
|
|
44
|
+
const httpReq = req;
|
|
45
|
+
// Extract Authorization header
|
|
46
|
+
const authHeader = (_httpReq_headers = httpReq.headers) === null || _httpReq_headers === void 0 ? void 0 : _httpReq_headers.authorization;
|
|
47
|
+
if (!authHeader) {
|
|
48
|
+
throw new Error('JWTUserAuth: No Authorization header found');
|
|
49
|
+
}
|
|
50
|
+
// Parse Bearer token
|
|
51
|
+
const match = /^Bearer\s+(.+)$/i.exec(authHeader.trim());
|
|
52
|
+
if (!match) {
|
|
53
|
+
throw new Error('JWTUserAuth: Invalid Authorization header format (expected "Bearer <token>")');
|
|
54
|
+
}
|
|
55
|
+
const token = match[1];
|
|
56
|
+
if (!token) {
|
|
57
|
+
throw new Error('JWTUserAuth: Empty JWT token');
|
|
58
|
+
}
|
|
59
|
+
// Verify JWT and extract payload
|
|
60
|
+
const payload = await this.verifyToken(token);
|
|
61
|
+
// Extract user ID from configured claim
|
|
62
|
+
const userId = payload[this.config.userIdClaim];
|
|
63
|
+
if (!userId || typeof userId !== 'string') {
|
|
64
|
+
throw new Error(`JWTUserAuth: JWT missing or invalid '${this.config.userIdClaim}' claim`);
|
|
65
|
+
}
|
|
66
|
+
return userId;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Verify JWT signature and claims
|
|
70
|
+
*/ async verifyToken(token) {
|
|
71
|
+
try {
|
|
72
|
+
// Build verification options
|
|
73
|
+
const options = {
|
|
74
|
+
...this.config.issuer && {
|
|
75
|
+
issuer: this.config.issuer
|
|
76
|
+
},
|
|
77
|
+
...this.config.audience && {
|
|
78
|
+
audience: this.config.audience
|
|
79
|
+
},
|
|
80
|
+
...this.config.clockTolerance && {
|
|
81
|
+
clockTolerance: this.config.clockTolerance
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
// Verify with appropriate key type
|
|
85
|
+
let result;
|
|
86
|
+
if (this.config.secret) {
|
|
87
|
+
// HS256 verification with shared secret
|
|
88
|
+
const secret = new TextEncoder().encode(this.config.secret);
|
|
89
|
+
result = await jwtVerify(token, secret, {
|
|
90
|
+
...options,
|
|
91
|
+
algorithms: this.config.algorithms.length > 0 ? this.config.algorithms : [
|
|
92
|
+
'HS256'
|
|
93
|
+
]
|
|
94
|
+
});
|
|
95
|
+
} else if (this.remoteJWKSet) {
|
|
96
|
+
// RS256/ES256 verification with remote JWKS
|
|
97
|
+
result = await jwtVerify(token, this.remoteJWKSet, {
|
|
98
|
+
...options,
|
|
99
|
+
algorithms: this.config.algorithms.length > 0 ? this.config.algorithms : [
|
|
100
|
+
'RS256',
|
|
101
|
+
'ES256'
|
|
102
|
+
]
|
|
103
|
+
});
|
|
104
|
+
} else if (this.config.publicKey) {
|
|
105
|
+
// RS256/ES256 verification with provided public key
|
|
106
|
+
// If string (PEM), import it first; if JWK, use directly
|
|
107
|
+
const key = typeof this.config.publicKey === 'string' ? await importSPKI(this.config.publicKey, 'RS256') : this.config.publicKey;
|
|
108
|
+
result = await jwtVerify(token, key, {
|
|
109
|
+
...options,
|
|
110
|
+
algorithms: this.config.algorithms.length > 0 ? this.config.algorithms : [
|
|
111
|
+
'RS256',
|
|
112
|
+
'ES256'
|
|
113
|
+
]
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
throw new Error('JWTUserAuth: No verification key configured');
|
|
117
|
+
}
|
|
118
|
+
return result.payload;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (error instanceof Error) {
|
|
121
|
+
throw new Error(`JWTUserAuth: JWT verification failed: ${error.message}`);
|
|
122
|
+
}
|
|
123
|
+
throw new Error('JWTUserAuth: JWT verification failed');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
constructor(config){
|
|
127
|
+
var _config_userIdClaim, _config_algorithms, _config_clockTolerance;
|
|
128
|
+
// Validate configuration
|
|
129
|
+
if (!config.secret && !config.publicKey && !config.jwksUrl) {
|
|
130
|
+
throw new Error('JWTUserAuth: Must provide one of: secret (HS256), publicKey (RS256/ES256), or jwksUrl');
|
|
131
|
+
}
|
|
132
|
+
if (config.secret && config.secret.length < 32) {
|
|
133
|
+
throw new Error('JWTUserAuth: secret must be at least 32 characters for HS256');
|
|
134
|
+
}
|
|
135
|
+
if ((config.secret ? 1 : 0) + (config.publicKey ? 1 : 0) + (config.jwksUrl ? 1 : 0) > 1) {
|
|
136
|
+
throw new Error('JWTUserAuth: Provide only one of: secret, publicKey, or jwksUrl');
|
|
137
|
+
}
|
|
138
|
+
// Store configuration with defaults
|
|
139
|
+
this.config = {
|
|
140
|
+
...config.secret !== undefined && {
|
|
141
|
+
secret: config.secret
|
|
142
|
+
},
|
|
143
|
+
...config.publicKey !== undefined && {
|
|
144
|
+
publicKey: config.publicKey
|
|
145
|
+
},
|
|
146
|
+
...config.jwksUrl !== undefined && {
|
|
147
|
+
jwksUrl: config.jwksUrl
|
|
148
|
+
},
|
|
149
|
+
...config.issuer !== undefined && {
|
|
150
|
+
issuer: config.issuer
|
|
151
|
+
},
|
|
152
|
+
...config.audience !== undefined && {
|
|
153
|
+
audience: config.audience
|
|
154
|
+
},
|
|
155
|
+
userIdClaim: (_config_userIdClaim = config.userIdClaim) !== null && _config_userIdClaim !== void 0 ? _config_userIdClaim : 'sub',
|
|
156
|
+
algorithms: (_config_algorithms = config.algorithms) !== null && _config_algorithms !== void 0 ? _config_algorithms : [],
|
|
157
|
+
clockTolerance: (_config_clockTolerance = config.clockTolerance) !== null && _config_clockTolerance !== void 0 ? _config_clockTolerance : 0
|
|
158
|
+
};
|
|
159
|
+
// Create remote JWK set if using JWKS URL
|
|
160
|
+
if (config.jwksUrl) {
|
|
161
|
+
this.remoteJWKSet = createRemoteJWKSet(new URL(config.jwksUrl));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/jwt-auth.ts"],"sourcesContent":["/**\n * JWT-based user authentication for multi-tenant deployments\n *\n * Extracts user ID from JWT tokens with signature and claims verification.\n * Supports HS256, RS256, ES256 algorithms via JOSE library.\n */\n\nimport { createRemoteJWKSet, importSPKI, type JWK, type JWTPayload, type JWTVerifyOptions, type JWTVerifyResult, jwtVerify } from 'jose';\nimport type { JWTUserAuthConfig, UserAuthProvider } from './types.ts';\n\n/**\n * HTTP request interface (subset needed for JWT auth)\n */\ninterface HttpRequest {\n headers?: {\n authorization?: string;\n };\n}\n\n/**\n * JWT-based user authentication provider\n *\n * Verifies JWT tokens and extracts user IDs from claims.\n * Use for multi-tenant deployments where users authenticate via JWT.\n *\n * @example\n * ```typescript\n * // HS256 with shared secret\n * const userAuth = new JWTUserAuth({\n * secret: process.env.JWT_SECRET!,\n * issuer: 'https://auth.example.com',\n * audience: 'api.example.com',\n * });\n *\n * // RS256 with public key\n * const userAuth = new JWTUserAuth({\n * publicKey: process.env.JWT_PUBLIC_KEY!,\n * issuer: 'https://auth.example.com',\n * });\n *\n * // RS256 with JWKS URL (dynamic key rotation)\n * const userAuth = new JWTUserAuth({\n * jwksUrl: 'https://auth.example.com/.well-known/jwks.json',\n * issuer: 'https://auth.example.com',\n * audience: 'api.example.com',\n * });\n * ```\n */\nexport class JWTUserAuth implements UserAuthProvider {\n private readonly config: {\n secret?: string;\n publicKey?: string | JWK;\n jwksUrl?: string;\n issuer?: string | string[];\n audience?: string | string[];\n userIdClaim: string;\n algorithms: string[];\n clockTolerance: number;\n };\n private readonly remoteJWKSet?: ReturnType<typeof createRemoteJWKSet>;\n\n constructor(config: JWTUserAuthConfig) {\n // Validate configuration\n if (!config.secret && !config.publicKey && !config.jwksUrl) {\n throw new Error('JWTUserAuth: Must provide one of: secret (HS256), publicKey (RS256/ES256), or jwksUrl');\n }\n\n if (config.secret && config.secret.length < 32) {\n throw new Error('JWTUserAuth: secret must be at least 32 characters for HS256');\n }\n\n if ((config.secret ? 1 : 0) + (config.publicKey ? 1 : 0) + (config.jwksUrl ? 1 : 0) > 1) {\n throw new Error('JWTUserAuth: Provide only one of: secret, publicKey, or jwksUrl');\n }\n\n // Store configuration with defaults\n this.config = {\n ...(config.secret !== undefined && { secret: config.secret }),\n ...(config.publicKey !== undefined && { publicKey: config.publicKey }),\n ...(config.jwksUrl !== undefined && { jwksUrl: config.jwksUrl }),\n ...(config.issuer !== undefined && { issuer: config.issuer }),\n ...(config.audience !== undefined && { audience: config.audience }),\n userIdClaim: config.userIdClaim ?? 'sub',\n algorithms: config.algorithms ?? [],\n clockTolerance: config.clockTolerance ?? 0,\n };\n\n // Create remote JWK set if using JWKS URL\n if (config.jwksUrl) {\n this.remoteJWKSet = createRemoteJWKSet(new URL(config.jwksUrl));\n }\n }\n\n /**\n * Extract and verify user ID from JWT token\n *\n * @param req - HTTP request object with Authorization header\n * @returns User ID from verified JWT claims\n * @throws Error if token missing, invalid, expired, or claims invalid\n */\n async getUserId(req: unknown): Promise<string> {\n const httpReq = req as HttpRequest;\n\n // Extract Authorization header\n const authHeader = httpReq.headers?.authorization;\n if (!authHeader) {\n throw new Error('JWTUserAuth: No Authorization header found');\n }\n\n // Parse Bearer token\n const match = /^Bearer\\s+(.+)$/i.exec(authHeader.trim());\n if (!match) {\n throw new Error('JWTUserAuth: Invalid Authorization header format (expected \"Bearer <token>\")');\n }\n\n const token = match[1];\n if (!token) {\n throw new Error('JWTUserAuth: Empty JWT token');\n }\n\n // Verify JWT and extract payload\n const payload = await this.verifyToken(token);\n\n // Extract user ID from configured claim\n const userId = payload[this.config.userIdClaim];\n if (!userId || typeof userId !== 'string') {\n throw new Error(`JWTUserAuth: JWT missing or invalid '${this.config.userIdClaim}' claim`);\n }\n\n return userId;\n }\n\n /**\n * Verify JWT signature and claims\n */\n private async verifyToken(token: string): Promise<JWTPayload> {\n try {\n // Build verification options\n const options: JWTVerifyOptions = {\n ...(this.config.issuer && { issuer: this.config.issuer }),\n ...(this.config.audience && { audience: this.config.audience }),\n ...(this.config.clockTolerance && { clockTolerance: this.config.clockTolerance }),\n };\n\n // Verify with appropriate key type\n let result: JWTVerifyResult;\n\n if (this.config.secret) {\n // HS256 verification with shared secret\n const secret = new TextEncoder().encode(this.config.secret);\n result = await jwtVerify(token, secret, {\n ...options,\n algorithms: this.config.algorithms.length > 0 ? this.config.algorithms : ['HS256'],\n });\n } else if (this.remoteJWKSet) {\n // RS256/ES256 verification with remote JWKS\n result = await jwtVerify(token, this.remoteJWKSet, {\n ...options,\n algorithms: this.config.algorithms.length > 0 ? this.config.algorithms : ['RS256', 'ES256'],\n });\n } else if (this.config.publicKey) {\n // RS256/ES256 verification with provided public key\n // If string (PEM), import it first; if JWK, use directly\n const key = typeof this.config.publicKey === 'string' ? await importSPKI(this.config.publicKey, 'RS256') : this.config.publicKey;\n\n result = await jwtVerify(token, key, {\n ...options,\n algorithms: this.config.algorithms.length > 0 ? this.config.algorithms : ['RS256', 'ES256'],\n });\n } else {\n throw new Error('JWTUserAuth: No verification key configured');\n }\n\n return result.payload;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`JWTUserAuth: JWT verification failed: ${error.message}`);\n }\n throw new Error('JWTUserAuth: JWT verification failed');\n }\n }\n}\n"],"names":["createRemoteJWKSet","importSPKI","jwtVerify","JWTUserAuth","getUserId","req","httpReq","authHeader","headers","authorization","Error","match","exec","trim","token","payload","verifyToken","userId","config","userIdClaim","options","issuer","audience","clockTolerance","result","secret","TextEncoder","encode","algorithms","length","remoteJWKSet","publicKey","key","error","message","jwksUrl","undefined","URL"],"mappings":"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"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key generation utilities for consistent storage key format
|
|
3
|
+
*
|
|
4
|
+
* Key format: {accountId}:{service}:{type}
|
|
5
|
+
* Example: work@gmail.com:gmail:token
|
|
6
|
+
*/
|
|
7
|
+
import type { Keyv } from 'keyv';
|
|
8
|
+
/**
|
|
9
|
+
* Key types for account-scoped data (requires accountId)
|
|
10
|
+
*/
|
|
11
|
+
export type AccountKeyType = 'token' | 'metadata' | 'dcr-client';
|
|
12
|
+
/**
|
|
13
|
+
* Key types for service-scoped data (no accountId)
|
|
14
|
+
*/
|
|
15
|
+
export type ServiceKeyType = 'active' | 'linked';
|
|
16
|
+
/**
|
|
17
|
+
* Parameters for account-scoped keys.
|
|
18
|
+
* All fields are required - no silent defaults.
|
|
19
|
+
*/
|
|
20
|
+
export interface AccountKeyParams {
|
|
21
|
+
/** Account identifier - typically an email address */
|
|
22
|
+
accountId: string;
|
|
23
|
+
/** Service name (e.g., 'gmail', 'sheets', 'drive', 'outlook') */
|
|
24
|
+
service: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parameters for service-scoped keys.
|
|
28
|
+
* These keys don't include accountId.
|
|
29
|
+
*/
|
|
30
|
+
export interface ServiceKeyParams {
|
|
31
|
+
/** Service name */
|
|
32
|
+
service: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create account-scoped storage key.
|
|
36
|
+
*
|
|
37
|
+
* These keys are scoped to a specific account (email address) and store
|
|
38
|
+
* account-specific data like OAuth tokens and account metadata.
|
|
39
|
+
*
|
|
40
|
+
* @param type - Key type ('token' for OAuth tokens, 'metadata' for account details, 'dcr-client' for DCR registration)
|
|
41
|
+
* @param params - Account key parameters with explicit field names
|
|
42
|
+
* @returns Storage key in format: "{accountId}:{service}:{type}"
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* // Store OAuth token
|
|
47
|
+
* const tokenKey = createAccountKey('token', {
|
|
48
|
+
* accountId: 'alice@gmail.com',
|
|
49
|
+
* service: 'gmail'
|
|
50
|
+
* });
|
|
51
|
+
* // Returns: "alice@gmail.com:gmail:token"
|
|
52
|
+
*
|
|
53
|
+
* // Store account metadata (alias, timestamps, profile)
|
|
54
|
+
* const metadataKey = createAccountKey('metadata', {
|
|
55
|
+
* accountId: 'alice@gmail.com',
|
|
56
|
+
* service: 'gmail'
|
|
57
|
+
* });
|
|
58
|
+
* // Returns: "alice@gmail.com:gmail:metadata"
|
|
59
|
+
*
|
|
60
|
+
* // Store DCR client registration info
|
|
61
|
+
* const dcrKey = createAccountKey('dcr-client', {
|
|
62
|
+
* accountId: 'alice@outlook.com',
|
|
63
|
+
* service: 'outlook'
|
|
64
|
+
* });
|
|
65
|
+
* // Returns: "alice@outlook.com:outlook:dcr-client"
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare function createAccountKey(type: AccountKeyType, params: AccountKeyParams): string;
|
|
69
|
+
/**
|
|
70
|
+
* Create service-scoped storage key.
|
|
71
|
+
*
|
|
72
|
+
* These keys are scoped to a service (not a specific account) and store
|
|
73
|
+
* service-level data like which account is active or the list of linked accounts.
|
|
74
|
+
*
|
|
75
|
+
* @param type - Key type ('active' for active account, 'linked' for account list)
|
|
76
|
+
* @param params - Service key parameters
|
|
77
|
+
* @returns Storage key in format: "{service}:{type}"
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```typescript
|
|
81
|
+
* // Track active account
|
|
82
|
+
* const activeKey = createServiceKey('active', {
|
|
83
|
+
* service: 'gmail'
|
|
84
|
+
* });
|
|
85
|
+
* // Returns: "gmail:active"
|
|
86
|
+
*
|
|
87
|
+
* // Store list of linked accounts
|
|
88
|
+
* const linkedKey = createServiceKey('linked', {
|
|
89
|
+
* service: 'gmail'
|
|
90
|
+
* });
|
|
91
|
+
* // Returns: "gmail:linked"
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function createServiceKey(type: ServiceKeyType, params: ServiceKeyParams): string;
|
|
95
|
+
/**
|
|
96
|
+
* Parse token key to extract components
|
|
97
|
+
*
|
|
98
|
+
* @param key - Storage key to parse
|
|
99
|
+
* @returns Object with accountId and service, or undefined if invalid format
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* const parsed = parseTokenKey('user@gmail.com:gmail:token');
|
|
103
|
+
* // Returns: { accountId: 'user@gmail.com', service: 'gmail' }
|
|
104
|
+
*
|
|
105
|
+
* const invalid = parseTokenKey('invalid-key');
|
|
106
|
+
* // Returns: undefined
|
|
107
|
+
*/
|
|
108
|
+
export declare function parseTokenKey(key: string): {
|
|
109
|
+
accountId: string;
|
|
110
|
+
service: string;
|
|
111
|
+
} | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* List all account IDs for a service
|
|
114
|
+
*
|
|
115
|
+
* Iterates token keys and returns all accountIds that match the service.
|
|
116
|
+
* Encapsulates key format details for forward compatibility.
|
|
117
|
+
*
|
|
118
|
+
* @param store - Keyv store to iterate
|
|
119
|
+
* @param service - Service name
|
|
120
|
+
* @returns Array of account IDs (e.g., email addresses)
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* const accounts = await listAccountIds(store, 'gmail');
|
|
124
|
+
* // Returns: ['alice@gmail.com', 'bob@gmail.com']
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* // Empty array if no accounts found
|
|
128
|
+
* const empty = await listAccountIds(store, 'unknown-service');
|
|
129
|
+
* // Returns: []
|
|
130
|
+
*/
|
|
131
|
+
export declare function listAccountIds(store: Keyv, service: string): Promise<string[]>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key generation utilities for consistent storage key format
|
|
3
|
+
*
|
|
4
|
+
* Key format: {accountId}:{service}:{type}
|
|
5
|
+
* Example: work@gmail.com:gmail:token
|
|
6
|
+
*/ /**
|
|
7
|
+
* Validate key parameters don't contain colon delimiter
|
|
8
|
+
*/ function validateKeyParams(params) {
|
|
9
|
+
for (const [key, value] of Object.entries(params)){
|
|
10
|
+
if (typeof value !== 'string') {
|
|
11
|
+
throw new Error(`Key parameter '${key}' must be a string, got: ${typeof value}`);
|
|
12
|
+
}
|
|
13
|
+
if (value.includes(':')) {
|
|
14
|
+
throw new Error(`Key parameter '${key}' cannot contain colon character: ${value}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create account-scoped storage key.
|
|
20
|
+
*
|
|
21
|
+
* These keys are scoped to a specific account (email address) and store
|
|
22
|
+
* account-specific data like OAuth tokens and account metadata.
|
|
23
|
+
*
|
|
24
|
+
* @param type - Key type ('token' for OAuth tokens, 'metadata' for account details, 'dcr-client' for DCR registration)
|
|
25
|
+
* @param params - Account key parameters with explicit field names
|
|
26
|
+
* @returns Storage key in format: "{accountId}:{service}:{type}"
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* // Store OAuth token
|
|
31
|
+
* const tokenKey = createAccountKey('token', {
|
|
32
|
+
* accountId: 'alice@gmail.com',
|
|
33
|
+
* service: 'gmail'
|
|
34
|
+
* });
|
|
35
|
+
* // Returns: "alice@gmail.com:gmail:token"
|
|
36
|
+
*
|
|
37
|
+
* // Store account metadata (alias, timestamps, profile)
|
|
38
|
+
* const metadataKey = createAccountKey('metadata', {
|
|
39
|
+
* accountId: 'alice@gmail.com',
|
|
40
|
+
* service: 'gmail'
|
|
41
|
+
* });
|
|
42
|
+
* // Returns: "alice@gmail.com:gmail:metadata"
|
|
43
|
+
*
|
|
44
|
+
* // Store DCR client registration info
|
|
45
|
+
* const dcrKey = createAccountKey('dcr-client', {
|
|
46
|
+
* accountId: 'alice@outlook.com',
|
|
47
|
+
* service: 'outlook'
|
|
48
|
+
* });
|
|
49
|
+
* // Returns: "alice@outlook.com:outlook:dcr-client"
|
|
50
|
+
* ```
|
|
51
|
+
*/ export function createAccountKey(type, params) {
|
|
52
|
+
validateKeyParams(params);
|
|
53
|
+
return `${params.accountId}:${params.service}:${type}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create service-scoped storage key.
|
|
57
|
+
*
|
|
58
|
+
* These keys are scoped to a service (not a specific account) and store
|
|
59
|
+
* service-level data like which account is active or the list of linked accounts.
|
|
60
|
+
*
|
|
61
|
+
* @param type - Key type ('active' for active account, 'linked' for account list)
|
|
62
|
+
* @param params - Service key parameters
|
|
63
|
+
* @returns Storage key in format: "{service}:{type}"
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* // Track active account
|
|
68
|
+
* const activeKey = createServiceKey('active', {
|
|
69
|
+
* service: 'gmail'
|
|
70
|
+
* });
|
|
71
|
+
* // Returns: "gmail:active"
|
|
72
|
+
*
|
|
73
|
+
* // Store list of linked accounts
|
|
74
|
+
* const linkedKey = createServiceKey('linked', {
|
|
75
|
+
* service: 'gmail'
|
|
76
|
+
* });
|
|
77
|
+
* // Returns: "gmail:linked"
|
|
78
|
+
* ```
|
|
79
|
+
*/ export function createServiceKey(type, params) {
|
|
80
|
+
validateKeyParams(params);
|
|
81
|
+
return `${params.service}:${type}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Parse token key to extract components
|
|
85
|
+
*
|
|
86
|
+
* @param key - Storage key to parse
|
|
87
|
+
* @returns Object with accountId and service, or undefined if invalid format
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* const parsed = parseTokenKey('user@gmail.com:gmail:token');
|
|
91
|
+
* // Returns: { accountId: 'user@gmail.com', service: 'gmail' }
|
|
92
|
+
*
|
|
93
|
+
* const invalid = parseTokenKey('invalid-key');
|
|
94
|
+
* // Returns: undefined
|
|
95
|
+
*/ export function parseTokenKey(key) {
|
|
96
|
+
const parts = key.split(':');
|
|
97
|
+
if (parts.length !== 3 || parts[2] !== 'token' || !parts[0] || !parts[1]) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
accountId: parts[0],
|
|
102
|
+
service: parts[1]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* List all account IDs for a service
|
|
107
|
+
*
|
|
108
|
+
* Iterates token keys and returns all accountIds that match the service.
|
|
109
|
+
* Encapsulates key format details for forward compatibility.
|
|
110
|
+
*
|
|
111
|
+
* @param store - Keyv store to iterate
|
|
112
|
+
* @param service - Service name
|
|
113
|
+
* @returns Array of account IDs (e.g., email addresses)
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* const accounts = await listAccountIds(store, 'gmail');
|
|
117
|
+
* // Returns: ['alice@gmail.com', 'bob@gmail.com']
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* // Empty array if no accounts found
|
|
121
|
+
* const empty = await listAccountIds(store, 'unknown-service');
|
|
122
|
+
* // Returns: []
|
|
123
|
+
*/ export async function listAccountIds(store, service) {
|
|
124
|
+
const accountIds = [];
|
|
125
|
+
try {
|
|
126
|
+
var _store_iterator;
|
|
127
|
+
const iterator = (_store_iterator = store.iterator) === null || _store_iterator === void 0 ? void 0 : _store_iterator.call(store, undefined);
|
|
128
|
+
if (!iterator) {
|
|
129
|
+
return accountIds;
|
|
130
|
+
}
|
|
131
|
+
for await (const [key] of iterator){
|
|
132
|
+
const parsed = parseTokenKey(key);
|
|
133
|
+
if (parsed && parsed.service === service) {
|
|
134
|
+
accountIds.push(parsed.accountId);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} catch (_error) {
|
|
138
|
+
// If iteration fails, return empty array (fail gracefully)
|
|
139
|
+
// This handles stores that don't support iteration
|
|
140
|
+
return accountIds;
|
|
141
|
+
}
|
|
142
|
+
return accountIds;
|
|
143
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/key-utils.ts"],"sourcesContent":["/**\n * Key generation utilities for consistent storage key format\n *\n * Key format: {accountId}:{service}:{type}\n * Example: work@gmail.com:gmail:token\n */\n\nimport type { Keyv } from 'keyv';\n\n/**\n * Key types for account-scoped data (requires accountId)\n */\nexport type AccountKeyType = 'token' | 'metadata' | 'dcr-client';\n\n/**\n * Key types for service-scoped data (no accountId)\n */\nexport type ServiceKeyType = 'active' | 'linked';\n\n/**\n * Parameters for account-scoped keys.\n * All fields are required - no silent defaults.\n */\nexport interface AccountKeyParams {\n /** Account identifier - typically an email address */\n accountId: string;\n\n /** Service name (e.g., 'gmail', 'sheets', 'drive', 'outlook') */\n service: string;\n}\n\n/**\n * Parameters for service-scoped keys.\n * These keys don't include accountId.\n */\nexport interface ServiceKeyParams {\n /** Service name */\n service: string;\n}\n\n/**\n * Validate key parameters don't contain colon delimiter\n */\nfunction validateKeyParams(params: AccountKeyParams | ServiceKeyParams): void {\n for (const [key, value] of Object.entries(params)) {\n if (typeof value !== 'string') {\n throw new Error(`Key parameter '${key}' must be a string, got: ${typeof value}`);\n }\n if (value.includes(':')) {\n throw new Error(`Key parameter '${key}' cannot contain colon character: ${value}`);\n }\n }\n}\n\n/**\n * Create account-scoped storage key.\n *\n * These keys are scoped to a specific account (email address) and store\n * account-specific data like OAuth tokens and account metadata.\n *\n * @param type - Key type ('token' for OAuth tokens, 'metadata' for account details, 'dcr-client' for DCR registration)\n * @param params - Account key parameters with explicit field names\n * @returns Storage key in format: \"{accountId}:{service}:{type}\"\n *\n * @example\n * ```typescript\n * // Store OAuth token\n * const tokenKey = createAccountKey('token', {\n * accountId: 'alice@gmail.com',\n * service: 'gmail'\n * });\n * // Returns: \"alice@gmail.com:gmail:token\"\n *\n * // Store account metadata (alias, timestamps, profile)\n * const metadataKey = createAccountKey('metadata', {\n * accountId: 'alice@gmail.com',\n * service: 'gmail'\n * });\n * // Returns: \"alice@gmail.com:gmail:metadata\"\n *\n * // Store DCR client registration info\n * const dcrKey = createAccountKey('dcr-client', {\n * accountId: 'alice@outlook.com',\n * service: 'outlook'\n * });\n * // Returns: \"alice@outlook.com:outlook:dcr-client\"\n * ```\n */\nexport function createAccountKey(type: AccountKeyType, params: AccountKeyParams): string {\n validateKeyParams(params);\n return `${params.accountId}:${params.service}:${type}`;\n}\n\n/**\n * Create service-scoped storage key.\n *\n * These keys are scoped to a service (not a specific account) and store\n * service-level data like which account is active or the list of linked accounts.\n *\n * @param type - Key type ('active' for active account, 'linked' for account list)\n * @param params - Service key parameters\n * @returns Storage key in format: \"{service}:{type}\"\n *\n * @example\n * ```typescript\n * // Track active account\n * const activeKey = createServiceKey('active', {\n * service: 'gmail'\n * });\n * // Returns: \"gmail:active\"\n *\n * // Store list of linked accounts\n * const linkedKey = createServiceKey('linked', {\n * service: 'gmail'\n * });\n * // Returns: \"gmail:linked\"\n * ```\n */\nexport function createServiceKey(type: ServiceKeyType, params: ServiceKeyParams): string {\n validateKeyParams(params);\n return `${params.service}:${type}`;\n}\n\n/**\n * Parse token key to extract components\n *\n * @param key - Storage key to parse\n * @returns Object with accountId and service, or undefined if invalid format\n *\n * @example\n * const parsed = parseTokenKey('user@gmail.com:gmail:token');\n * // Returns: { accountId: 'user@gmail.com', service: 'gmail' }\n *\n * const invalid = parseTokenKey('invalid-key');\n * // Returns: undefined\n */\nexport function parseTokenKey(key: string): { accountId: string; service: string } | undefined {\n const parts = key.split(':');\n if (parts.length !== 3 || parts[2] !== 'token' || !parts[0] || !parts[1]) {\n return undefined;\n }\n return {\n accountId: parts[0],\n service: parts[1],\n };\n}\n\n/**\n * List all account IDs for a service\n *\n * Iterates token keys and returns all accountIds that match the service.\n * Encapsulates key format details for forward compatibility.\n *\n * @param store - Keyv store to iterate\n * @param service - Service name\n * @returns Array of account IDs (e.g., email addresses)\n *\n * @example\n * const accounts = await listAccountIds(store, 'gmail');\n * // Returns: ['alice@gmail.com', 'bob@gmail.com']\n *\n * @example\n * // Empty array if no accounts found\n * const empty = await listAccountIds(store, 'unknown-service');\n * // Returns: []\n */\nexport async function listAccountIds(store: Keyv, service: string): Promise<string[]> {\n const accountIds: string[] = [];\n\n try {\n const iterator = store.iterator?.(undefined);\n if (!iterator) {\n return accountIds;\n }\n\n for await (const [key] of iterator) {\n const parsed = parseTokenKey(key);\n if (parsed && parsed.service === service) {\n accountIds.push(parsed.accountId);\n }\n }\n } catch (_error) {\n // If iteration fails, return empty array (fail gracefully)\n // This handles stores that don't support iteration\n return accountIds;\n }\n\n return accountIds;\n}\n"],"names":["validateKeyParams","params","key","value","Object","entries","Error","includes","createAccountKey","type","accountId","service","createServiceKey","parseTokenKey","parts","split","length","undefined","listAccountIds","store","accountIds","iterator","parsed","push","_error"],"mappings":"AAAA;;;;;CAKC,GAmCD;;CAEC,GACD,SAASA,kBAAkBC,MAA2C;IACpE,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIC,OAAOC,OAAO,CAACJ,QAAS;QACjD,IAAI,OAAOE,UAAU,UAAU;YAC7B,MAAM,IAAIG,MAAM,CAAC,eAAe,EAAEJ,IAAI,yBAAyB,EAAE,OAAOC,OAAO;QACjF;QACA,IAAIA,MAAMI,QAAQ,CAAC,MAAM;YACvB,MAAM,IAAID,MAAM,CAAC,eAAe,EAAEJ,IAAI,kCAAkC,EAAEC,OAAO;QACnF;IACF;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCC,GACD,OAAO,SAASK,iBAAiBC,IAAoB,EAAER,MAAwB;IAC7ED,kBAAkBC;IAClB,OAAO,GAAGA,OAAOS,SAAS,CAAC,CAAC,EAAET,OAAOU,OAAO,CAAC,CAAC,EAAEF,MAAM;AACxD;AAEA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,SAASG,iBAAiBH,IAAoB,EAAER,MAAwB;IAC7ED,kBAAkBC;IAClB,OAAO,GAAGA,OAAOU,OAAO,CAAC,CAAC,EAAEF,MAAM;AACpC;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASI,cAAcX,GAAW;IACvC,MAAMY,QAAQZ,IAAIa,KAAK,CAAC;IACxB,IAAID,MAAME,MAAM,KAAK,KAAKF,KAAK,CAAC,EAAE,KAAK,WAAW,CAACA,KAAK,CAAC,EAAE,IAAI,CAACA,KAAK,CAAC,EAAE,EAAE;QACxE,OAAOG;IACT;IACA,OAAO;QACLP,WAAWI,KAAK,CAAC,EAAE;QACnBH,SAASG,KAAK,CAAC,EAAE;IACnB;AACF;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,eAAeI,eAAeC,KAAW,EAAER,OAAe;IAC/D,MAAMS,aAAuB,EAAE;IAE/B,IAAI;YACeD;QAAjB,MAAME,YAAWF,kBAAAA,MAAME,QAAQ,cAAdF,sCAAAA,qBAAAA,OAAiBF;QAClC,IAAI,CAACI,UAAU;YACb,OAAOD;QACT;QAEA,WAAW,MAAM,CAAClB,IAAI,IAAImB,SAAU;YAClC,MAAMC,SAAST,cAAcX;YAC7B,IAAIoB,UAAUA,OAAOX,OAAO,KAAKA,SAAS;gBACxCS,WAAWG,IAAI,CAACD,OAAOZ,SAAS;YAClC;QACF;IACF,EAAE,OAAOc,QAAQ;QACf,2DAA2D;QAC3D,mDAAmD;QACnD,OAAOJ;IACT;IAEA,OAAOA;AACT"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified account management API for MCP servers.
|
|
3
|
+
*
|
|
4
|
+
* Provides two account management modes:
|
|
5
|
+
* - Loopback: Server-managed multi-account OAuth (LoopbackOAuthProvider)
|
|
6
|
+
* - Stateless: MCP client-managed OAuth (read-only status)
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Loopback OAuth account management
|
|
10
|
+
* const {tools, prompts} = AccountServer.createLoopback({
|
|
11
|
+
* service: 'gmail',
|
|
12
|
+
* store: tokenStore,
|
|
13
|
+
* logger,
|
|
14
|
+
* auth: authProvider
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Stateless mode (MCP OAuth)
|
|
19
|
+
* const {tools, prompts} = AccountServer.createStateless({
|
|
20
|
+
* service: 'gmail'
|
|
21
|
+
* });
|
|
22
|
+
*/
|
|
23
|
+
import { createLoopback } from './loopback.js';
|
|
24
|
+
import { createStateless } from './stateless.js';
|
|
25
|
+
export declare const AccountServer: {
|
|
26
|
+
/**
|
|
27
|
+
* Create loopback OAuth account management tools.
|
|
28
|
+
* Server manages multiple accounts with stored tokens (LoopbackOAuthProvider).
|
|
29
|
+
* Returns 4 tools: account-me, account-switch, account-remove, account-list.
|
|
30
|
+
* No prompts.
|
|
31
|
+
*/
|
|
32
|
+
createLoopback: typeof createLoopback;
|
|
33
|
+
/**
|
|
34
|
+
* Create stateless mode tools.
|
|
35
|
+
* MCP client manages authentication. Server extracts user identity from bearer token.
|
|
36
|
+
* Returns 1 tool: account-me.
|
|
37
|
+
* No prompts.
|
|
38
|
+
*/
|
|
39
|
+
createStateless: typeof createStateless;
|
|
40
|
+
};
|
|
41
|
+
export { createLoopback } from './loopback.js';
|
|
42
|
+
export { createAccountMe } from './me.js';
|
|
43
|
+
export { findAccountByEmailOrAlias } from './shared-utils.js';
|
|
44
|
+
export { createStateless } from './stateless.js';
|
|
45
|
+
export type { AccountLoopbackConfig, AccountMeConfig, AccountStatelessConfig } from './types.js';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified account management API for MCP servers.
|
|
3
|
+
*
|
|
4
|
+
* Provides two account management modes:
|
|
5
|
+
* - Loopback: Server-managed multi-account OAuth (LoopbackOAuthProvider)
|
|
6
|
+
* - Stateless: MCP client-managed OAuth (read-only status)
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Loopback OAuth account management
|
|
10
|
+
* const {tools, prompts} = AccountServer.createLoopback({
|
|
11
|
+
* service: 'gmail',
|
|
12
|
+
* store: tokenStore,
|
|
13
|
+
* logger,
|
|
14
|
+
* auth: authProvider
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Stateless mode (MCP OAuth)
|
|
19
|
+
* const {tools, prompts} = AccountServer.createStateless({
|
|
20
|
+
* service: 'gmail'
|
|
21
|
+
* });
|
|
22
|
+
*/ import { createLoopback } from './loopback.js';
|
|
23
|
+
import { createStateless } from './stateless.js';
|
|
24
|
+
export const AccountServer = {
|
|
25
|
+
/**
|
|
26
|
+
* Create loopback OAuth account management tools.
|
|
27
|
+
* Server manages multiple accounts with stored tokens (LoopbackOAuthProvider).
|
|
28
|
+
* Returns 4 tools: account-me, account-switch, account-remove, account-list.
|
|
29
|
+
* No prompts.
|
|
30
|
+
*/ createLoopback,
|
|
31
|
+
/**
|
|
32
|
+
* Create stateless mode tools.
|
|
33
|
+
* MCP client manages authentication. Server extracts user identity from bearer token.
|
|
34
|
+
* Returns 1 tool: account-me.
|
|
35
|
+
* No prompts.
|
|
36
|
+
*/ createStateless
|
|
37
|
+
};
|
|
38
|
+
export { createLoopback } from './loopback.js';
|
|
39
|
+
export { createAccountMe } from './me.js';
|
|
40
|
+
export { findAccountByEmailOrAlias } from './shared-utils.js';
|
|
41
|
+
export { createStateless } from './stateless.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/lib/account-server/index.ts"],"sourcesContent":["/**\n * Unified account management API for MCP servers.\n *\n * Provides two account management modes:\n * - Loopback: Server-managed multi-account OAuth (LoopbackOAuthProvider)\n * - Stateless: MCP client-managed OAuth (read-only status)\n *\n * @example\n * // Loopback OAuth account management\n * const {tools, prompts} = AccountServer.createLoopback({\n * service: 'gmail',\n * store: tokenStore,\n * logger,\n * auth: authProvider\n * });\n *\n * @example\n * // Stateless mode (MCP OAuth)\n * const {tools, prompts} = AccountServer.createStateless({\n * service: 'gmail'\n * });\n */\n\nimport { createLoopback } from './loopback.ts';\nimport { createStateless } from './stateless.ts';\n\nexport const AccountServer = {\n /**\n * Create loopback OAuth account management tools.\n * Server manages multiple accounts with stored tokens (LoopbackOAuthProvider).\n * Returns 4 tools: account-me, account-switch, account-remove, account-list.\n * No prompts.\n */\n createLoopback,\n\n /**\n * Create stateless mode tools.\n * MCP client manages authentication. Server extracts user identity from bearer token.\n * Returns 1 tool: account-me.\n * No prompts.\n */\n createStateless,\n};\n\nexport { createLoopback } from './loopback.ts';\nexport { createAccountMe } from './me.ts';\nexport { findAccountByEmailOrAlias } from './shared-utils.ts';\nexport { createStateless } from './stateless.ts';\nexport type { AccountLoopbackConfig, AccountMeConfig, AccountStatelessConfig } from './types.ts';\n"],"names":["createLoopback","createStateless","AccountServer","createAccountMe","findAccountByEmailOrAlias"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;CAqBC,GAED,SAASA,cAAc,QAAQ,gBAAgB;AAC/C,SAASC,eAAe,QAAQ,iBAAiB;AAEjD,OAAO,MAAMC,gBAAgB;IAC3B;;;;;GAKC,GACDF;IAEA;;;;;GAKC,GACDC;AACF,EAAE;AAEF,SAASD,cAAc,QAAQ,gBAAgB;AAC/C,SAASG,eAAe,QAAQ,UAAU;AAC1C,SAASC,yBAAyB,QAAQ,oBAAoB;AAC9D,SAASH,eAAe,QAAQ,iBAAiB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loopback OAuth account management tools.
|
|
3
|
+
*
|
|
4
|
+
* Provides account management for LoopbackOAuthProvider (server-managed multi-account).
|
|
5
|
+
* Users can add multiple accounts, switch between them, and manage identities.
|
|
6
|
+
*
|
|
7
|
+
* Tools:
|
|
8
|
+
* - account-me: Show current user identity (email, alias, session expiry)
|
|
9
|
+
* - account-switch: Use account (add if needed, switch if already linked)
|
|
10
|
+
* - account-remove: Remove account and delete tokens
|
|
11
|
+
* - account-list: Show all linked accounts (returns empty array if none)
|
|
12
|
+
*/
|
|
13
|
+
import type { McpPrompt, McpTool } from '../../types.js';
|
|
14
|
+
import type { AccountLoopbackConfig } from './types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Create loopback OAuth account management tools.
|
|
17
|
+
* Returns 4 tools: account-me, account-switch, account-remove, account-list.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createLoopback(config: AccountLoopbackConfig): {
|
|
20
|
+
tools: McpTool[];
|
|
21
|
+
prompts: McpPrompt[];
|
|
22
|
+
};
|