@vinkius-core/mcp-fusion-api-key 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/dist/ApiKeyManager.d.ts +108 -0
- package/dist/ApiKeyManager.d.ts.map +1 -0
- package/dist/ApiKeyManager.js +157 -0
- package/dist/ApiKeyManager.js.map +1 -0
- package/dist/createApiKeyTool.d.ts +38 -0
- package/dist/createApiKeyTool.d.ts.map +1 -0
- package/dist/createApiKeyTool.js +94 -0
- package/dist/createApiKeyTool.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +45 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +94 -0
- package/dist/middleware.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key Manager — Validation & Hashing
|
|
3
|
+
*
|
|
4
|
+
* Validates API keys using multiple strategies:
|
|
5
|
+
* - Static key set (in-memory)
|
|
6
|
+
* - SHA-256 hash comparison (for safe storage)
|
|
7
|
+
* - Async validator function (database lookup)
|
|
8
|
+
*
|
|
9
|
+
* All comparisons use timing-safe operations to prevent timing attacks.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { ApiKeyManager } from '@vinkius-core/mcp-fusion-api-key';
|
|
14
|
+
*
|
|
15
|
+
* // Static keys
|
|
16
|
+
* const manager = new ApiKeyManager({
|
|
17
|
+
* keys: ['sk_live_abc123', 'sk_live_def456'],
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // Hash-based (for DB storage)
|
|
21
|
+
* const hash = ApiKeyManager.hashKey('sk_live_abc123');
|
|
22
|
+
* const manager = new ApiKeyManager({ hashedKeys: [hash] });
|
|
23
|
+
*
|
|
24
|
+
* // Async validator (DB lookup)
|
|
25
|
+
* const manager = new ApiKeyManager({
|
|
26
|
+
* validator: async (key) => {
|
|
27
|
+
* const record = await db.apiKeys.findByKey(key);
|
|
28
|
+
* return record ? { valid: true, metadata: { userId: record.userId } } : { valid: false };
|
|
29
|
+
* },
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export interface ApiKeyManagerConfig {
|
|
34
|
+
/** Static set of valid API keys (plaintext). */
|
|
35
|
+
readonly keys?: readonly string[];
|
|
36
|
+
/** Set of pre-hashed keys (SHA-256 hex). Use `ApiKeyManager.hashKey()` to generate. */
|
|
37
|
+
readonly hashedKeys?: readonly string[];
|
|
38
|
+
/**
|
|
39
|
+
* Async validator function for dynamic key lookup (e.g., database).
|
|
40
|
+
* Takes priority over `keys` and `hashedKeys` when provided.
|
|
41
|
+
*/
|
|
42
|
+
readonly validator?: (key: string) => Promise<ApiKeyValidationResult>;
|
|
43
|
+
/** Prefix required on API keys (e.g., 'sk_live_'). Optional. */
|
|
44
|
+
readonly prefix?: string;
|
|
45
|
+
/** Minimum key length. Default: 16 */
|
|
46
|
+
readonly minLength?: number;
|
|
47
|
+
}
|
|
48
|
+
export interface ApiKeyValidationResult {
|
|
49
|
+
/** Whether the key is valid. */
|
|
50
|
+
readonly valid: boolean;
|
|
51
|
+
/** Optional metadata about the key owner (userId, scopes, etc.). */
|
|
52
|
+
readonly metadata?: Record<string, unknown>;
|
|
53
|
+
/** Reason for rejection (only when valid is false). */
|
|
54
|
+
readonly reason?: string;
|
|
55
|
+
}
|
|
56
|
+
export declare class ApiKeyManager {
|
|
57
|
+
private readonly _config;
|
|
58
|
+
private readonly _minLength;
|
|
59
|
+
private readonly _keyHashes;
|
|
60
|
+
constructor(config: ApiKeyManagerConfig);
|
|
61
|
+
/**
|
|
62
|
+
* Validate an API key.
|
|
63
|
+
*
|
|
64
|
+
* @param key - Raw API key string
|
|
65
|
+
* @returns Validation result with optional metadata
|
|
66
|
+
*/
|
|
67
|
+
validate(key: string): Promise<ApiKeyValidationResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Quick boolean check without detailed result.
|
|
70
|
+
*/
|
|
71
|
+
isValid(key: string): Promise<boolean>;
|
|
72
|
+
/**
|
|
73
|
+
* Hash an API key using SHA-256 for safe storage.
|
|
74
|
+
*
|
|
75
|
+
* @param rawKey - Plaintext API key
|
|
76
|
+
* @returns Hex-encoded SHA-256 hash
|
|
77
|
+
*/
|
|
78
|
+
static hashKey(rawKey: string): string;
|
|
79
|
+
/**
|
|
80
|
+
* Compare a raw key against a stored hash using timing-safe comparison.
|
|
81
|
+
*
|
|
82
|
+
* @param rawKey - Plaintext API key
|
|
83
|
+
* @param storedHash - SHA-256 hex hash to compare against
|
|
84
|
+
* @returns Whether the key matches the hash
|
|
85
|
+
*/
|
|
86
|
+
static matchKey(rawKey: string, storedHash: string): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Generate a random API key with optional prefix.
|
|
89
|
+
*
|
|
90
|
+
* @param options - Generation options
|
|
91
|
+
* @returns Random API key string
|
|
92
|
+
*/
|
|
93
|
+
static generateKey(options?: {
|
|
94
|
+
prefix?: string;
|
|
95
|
+
length?: number;
|
|
96
|
+
}): string;
|
|
97
|
+
/**
|
|
98
|
+
* Check if a hash exists in the pre-computed set using timing-safe comparison.
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
101
|
+
private _isHashInSet;
|
|
102
|
+
/**
|
|
103
|
+
* Timing-safe string comparison to prevent timing attacks.
|
|
104
|
+
* @internal
|
|
105
|
+
*/
|
|
106
|
+
private static _timingSafeCompare;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=ApiKeyManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApiKeyManager.d.ts","sourceRoot":"","sources":["../src/ApiKeyManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAQH,MAAM,WAAW,mBAAmB;IAChC,gDAAgD;IAChD,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAElC,uFAAuF;IACvF,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAExC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAEtE,gEAAgE;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,sCAAsC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,sBAAsB;IACnC,gCAAgC;IAChC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAExB,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE5C,uDAAuD;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC5B;AAMD,qBAAa,aAAa;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;gBAE7B,MAAM,EAAE,mBAAmB;IAuBvC;;;;;OAKG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA4B5D;;OAEG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO5C;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAItC;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAK5D;;;;;OAKG;IACH,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAS1E;;;OAGG;IACH,OAAO,CAAC,YAAY;IASpB;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;CAQpC"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key Manager — Validation & Hashing
|
|
3
|
+
*
|
|
4
|
+
* Validates API keys using multiple strategies:
|
|
5
|
+
* - Static key set (in-memory)
|
|
6
|
+
* - SHA-256 hash comparison (for safe storage)
|
|
7
|
+
* - Async validator function (database lookup)
|
|
8
|
+
*
|
|
9
|
+
* All comparisons use timing-safe operations to prevent timing attacks.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { ApiKeyManager } from '@vinkius-core/mcp-fusion-api-key';
|
|
14
|
+
*
|
|
15
|
+
* // Static keys
|
|
16
|
+
* const manager = new ApiKeyManager({
|
|
17
|
+
* keys: ['sk_live_abc123', 'sk_live_def456'],
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // Hash-based (for DB storage)
|
|
21
|
+
* const hash = ApiKeyManager.hashKey('sk_live_abc123');
|
|
22
|
+
* const manager = new ApiKeyManager({ hashedKeys: [hash] });
|
|
23
|
+
*
|
|
24
|
+
* // Async validator (DB lookup)
|
|
25
|
+
* const manager = new ApiKeyManager({
|
|
26
|
+
* validator: async (key) => {
|
|
27
|
+
* const record = await db.apiKeys.findByKey(key);
|
|
28
|
+
* return record ? { valid: true, metadata: { userId: record.userId } } : { valid: false };
|
|
29
|
+
* },
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
import * as crypto from 'node:crypto';
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// ApiKeyManager
|
|
36
|
+
// ============================================================================
|
|
37
|
+
export class ApiKeyManager {
|
|
38
|
+
_config;
|
|
39
|
+
_minLength;
|
|
40
|
+
_keyHashes;
|
|
41
|
+
constructor(config) {
|
|
42
|
+
if (!config.keys?.length && !config.hashedKeys?.length && !config.validator) {
|
|
43
|
+
throw new Error('ApiKeyManager requires at least one of: keys, hashedKeys, validator');
|
|
44
|
+
}
|
|
45
|
+
this._config = config;
|
|
46
|
+
this._minLength = config.minLength ?? 16;
|
|
47
|
+
// Pre-hash static keys for timing-safe comparison
|
|
48
|
+
this._keyHashes = new Set();
|
|
49
|
+
if (config.keys) {
|
|
50
|
+
for (const key of config.keys) {
|
|
51
|
+
this._keyHashes.add(ApiKeyManager.hashKey(key));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (config.hashedKeys) {
|
|
55
|
+
for (const hash of config.hashedKeys) {
|
|
56
|
+
this._keyHashes.add(hash);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// ── Public API ───────────────────────────────────────
|
|
61
|
+
/**
|
|
62
|
+
* Validate an API key.
|
|
63
|
+
*
|
|
64
|
+
* @param key - Raw API key string
|
|
65
|
+
* @returns Validation result with optional metadata
|
|
66
|
+
*/
|
|
67
|
+
async validate(key) {
|
|
68
|
+
// Format checks
|
|
69
|
+
if (!key || typeof key !== 'string') {
|
|
70
|
+
return { valid: false, reason: 'API key is empty or not a string' };
|
|
71
|
+
}
|
|
72
|
+
if (key.length < this._minLength) {
|
|
73
|
+
return { valid: false, reason: `API key too short (min ${this._minLength} chars)` };
|
|
74
|
+
}
|
|
75
|
+
if (this._config.prefix && !key.startsWith(this._config.prefix)) {
|
|
76
|
+
return { valid: false, reason: `API key must start with '${this._config.prefix}'` };
|
|
77
|
+
}
|
|
78
|
+
// Async validator takes priority
|
|
79
|
+
if (this._config.validator) {
|
|
80
|
+
return this._config.validator(key);
|
|
81
|
+
}
|
|
82
|
+
// Hash-based comparison (timing-safe)
|
|
83
|
+
const keyHash = ApiKeyManager.hashKey(key);
|
|
84
|
+
const isValid = this._isHashInSet(keyHash);
|
|
85
|
+
return isValid
|
|
86
|
+
? { valid: true }
|
|
87
|
+
: { valid: false, reason: 'Invalid API key' };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Quick boolean check without detailed result.
|
|
91
|
+
*/
|
|
92
|
+
async isValid(key) {
|
|
93
|
+
const result = await this.validate(key);
|
|
94
|
+
return result.valid;
|
|
95
|
+
}
|
|
96
|
+
// ── Static Utilities ─────────────────────────────────
|
|
97
|
+
/**
|
|
98
|
+
* Hash an API key using SHA-256 for safe storage.
|
|
99
|
+
*
|
|
100
|
+
* @param rawKey - Plaintext API key
|
|
101
|
+
* @returns Hex-encoded SHA-256 hash
|
|
102
|
+
*/
|
|
103
|
+
static hashKey(rawKey) {
|
|
104
|
+
return crypto.createHash('sha256').update(rawKey).digest('hex');
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Compare a raw key against a stored hash using timing-safe comparison.
|
|
108
|
+
*
|
|
109
|
+
* @param rawKey - Plaintext API key
|
|
110
|
+
* @param storedHash - SHA-256 hex hash to compare against
|
|
111
|
+
* @returns Whether the key matches the hash
|
|
112
|
+
*/
|
|
113
|
+
static matchKey(rawKey, storedHash) {
|
|
114
|
+
const keyHash = ApiKeyManager.hashKey(rawKey);
|
|
115
|
+
return ApiKeyManager._timingSafeCompare(keyHash, storedHash);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Generate a random API key with optional prefix.
|
|
119
|
+
*
|
|
120
|
+
* @param options - Generation options
|
|
121
|
+
* @returns Random API key string
|
|
122
|
+
*/
|
|
123
|
+
static generateKey(options) {
|
|
124
|
+
const prefix = options?.prefix ?? 'sk_';
|
|
125
|
+
const length = options?.length ?? 32;
|
|
126
|
+
const random = crypto.randomBytes(length).toString('base64url').slice(0, length);
|
|
127
|
+
return `${prefix}${random}`;
|
|
128
|
+
}
|
|
129
|
+
// ── Private Helpers ──────────────────────────────────
|
|
130
|
+
/**
|
|
131
|
+
* Check if a hash exists in the pre-computed set using timing-safe comparison.
|
|
132
|
+
* @internal
|
|
133
|
+
*/
|
|
134
|
+
_isHashInSet(hash) {
|
|
135
|
+
for (const stored of this._keyHashes) {
|
|
136
|
+
if (ApiKeyManager._timingSafeCompare(hash, stored)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Timing-safe string comparison to prevent timing attacks.
|
|
144
|
+
* @internal
|
|
145
|
+
*/
|
|
146
|
+
static _timingSafeCompare(a, b) {
|
|
147
|
+
if (a.length !== b.length)
|
|
148
|
+
return false;
|
|
149
|
+
try {
|
|
150
|
+
return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=ApiKeyManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApiKeyManager.js","sourceRoot":"","sources":["../src/ApiKeyManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key Auth Tool Factory — Pre-built API Key Validation Tool
|
|
3
|
+
*
|
|
4
|
+
* Creates a complete mcp-fusion tool with validate and status actions.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { createApiKeyTool } from '@vinkius-core/mcp-fusion-api-key';
|
|
9
|
+
*
|
|
10
|
+
* const apiKeyTool = createApiKeyTool({
|
|
11
|
+
* keys: ['sk_live_abc123'],
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import type { ApiKeyManagerConfig } from './ApiKeyManager.js';
|
|
16
|
+
export interface ApiKeyToolConfig<TContext = unknown> extends ApiKeyManagerConfig {
|
|
17
|
+
/** Tool name in MCP. Default: 'api_key_auth' */
|
|
18
|
+
readonly toolName?: string;
|
|
19
|
+
/** Tool description for the LLM. */
|
|
20
|
+
readonly description?: string;
|
|
21
|
+
/** Tags for selective tool exposure. */
|
|
22
|
+
readonly tags?: string[];
|
|
23
|
+
/** Extract API key from context for the `status` action. */
|
|
24
|
+
readonly extractKey?: (ctx: TContext) => string | null | undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Creates a complete API key auth tool with validate and status actions.
|
|
28
|
+
*
|
|
29
|
+
* Actions:
|
|
30
|
+
* - `validate` — Validate an API key and return metadata
|
|
31
|
+
* - `status` — Check if API key is present/valid
|
|
32
|
+
*/
|
|
33
|
+
export declare function createApiKeyTool<TContext = unknown>(config: ApiKeyToolConfig<TContext>): import("@vinkius-core/mcp-fusion").GroupedToolBuilder<TContext, Record<string, never>, string, Record<string, never> & {
|
|
34
|
+
[x: `${string}.validate`]: Record<string, unknown>;
|
|
35
|
+
} & {
|
|
36
|
+
[x: `${string}.status`]: Record<string, unknown>;
|
|
37
|
+
}>;
|
|
38
|
+
//# sourceMappingURL=createApiKeyTool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createApiKeyTool.d.ts","sourceRoot":"","sources":["../src/createApiKeyTool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAM9D,MAAM,WAAW,gBAAgB,CAAC,QAAQ,GAAG,OAAO,CAAE,SAAQ,mBAAmB;IAC7E,gDAAgD;IAChD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAE3B,oCAAoC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAE9B,wCAAwC;IACxC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAEzB,4DAA4D;IAC5D,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACtE;AAkBD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,GAAG,OAAO,EAAE,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC;;;;GAkEtF"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key Auth Tool Factory — Pre-built API Key Validation Tool
|
|
3
|
+
*
|
|
4
|
+
* Creates a complete mcp-fusion tool with validate and status actions.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { createApiKeyTool } from '@vinkius-core/mcp-fusion-api-key';
|
|
9
|
+
*
|
|
10
|
+
* const apiKeyTool = createApiKeyTool({
|
|
11
|
+
* keys: ['sk_live_abc123'],
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import { createTool } from '@vinkius-core/mcp-fusion';
|
|
16
|
+
import { ApiKeyManager } from './ApiKeyManager.js';
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Response Helpers
|
|
19
|
+
// ============================================================================
|
|
20
|
+
function ok(data) {
|
|
21
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
22
|
+
}
|
|
23
|
+
function fail(data) {
|
|
24
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], isError: true };
|
|
25
|
+
}
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Factory
|
|
28
|
+
// ============================================================================
|
|
29
|
+
/**
|
|
30
|
+
* Creates a complete API key auth tool with validate and status actions.
|
|
31
|
+
*
|
|
32
|
+
* Actions:
|
|
33
|
+
* - `validate` — Validate an API key and return metadata
|
|
34
|
+
* - `status` — Check if API key is present/valid
|
|
35
|
+
*/
|
|
36
|
+
export function createApiKeyTool(config) {
|
|
37
|
+
const manager = new ApiKeyManager(config);
|
|
38
|
+
const toolName = config.toolName ?? 'api_key_auth';
|
|
39
|
+
const description = config.description ?? 'API key authentication — validate keys and check status';
|
|
40
|
+
const extractKey = config.extractKey;
|
|
41
|
+
const tool = createTool(toolName);
|
|
42
|
+
if (config.tags?.length) {
|
|
43
|
+
tool.tags(...config.tags);
|
|
44
|
+
}
|
|
45
|
+
return tool
|
|
46
|
+
.action({
|
|
47
|
+
name: 'validate',
|
|
48
|
+
description: 'Validate an API key',
|
|
49
|
+
handler: async (_ctx, args) => {
|
|
50
|
+
const key = args['key'];
|
|
51
|
+
if (!key) {
|
|
52
|
+
return fail({ message: 'API key is required' });
|
|
53
|
+
}
|
|
54
|
+
const result = await manager.validate(key);
|
|
55
|
+
if (!result.valid) {
|
|
56
|
+
return fail({
|
|
57
|
+
message: `Validation failed: ${result.reason}`,
|
|
58
|
+
valid: false,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return ok({
|
|
62
|
+
valid: true,
|
|
63
|
+
metadata: result.metadata,
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
.action({
|
|
68
|
+
name: 'status',
|
|
69
|
+
description: 'Check API key authentication status from context',
|
|
70
|
+
handler: async (ctx) => {
|
|
71
|
+
if (!extractKey) {
|
|
72
|
+
return ok({
|
|
73
|
+
available: false,
|
|
74
|
+
reason: 'No key extractor configured',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const key = extractKey(ctx);
|
|
78
|
+
if (!key) {
|
|
79
|
+
return ok({
|
|
80
|
+
authenticated: false,
|
|
81
|
+
reason: 'No API key found in context',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const result = await manager.validate(key);
|
|
85
|
+
return ok({
|
|
86
|
+
authenticated: result.valid,
|
|
87
|
+
valid: result.valid,
|
|
88
|
+
reason: result.reason,
|
|
89
|
+
metadata: result.metadata,
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=createApiKeyTool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createApiKeyTool.js","sourceRoot":"","sources":["../src/createApiKeyTool.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vinkius-core/mcp-fusion-api-key — API Key Validation for MCP Servers
|
|
3
|
+
*
|
|
4
|
+
* Timing-safe API key validation with SHA-256 hashing,
|
|
5
|
+
* async validators, and mcp-fusion middleware integration.
|
|
6
|
+
* Zero external dependencies — uses native Node.js crypto.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { ApiKeyManager, requireApiKey, createApiKeyTool } from '@vinkius-core/mcp-fusion-api-key';
|
|
11
|
+
*
|
|
12
|
+
* // Middleware
|
|
13
|
+
* const projects = createTool('projects')
|
|
14
|
+
* .use(requireApiKey({ keys: ['sk_live_abc123'] }))
|
|
15
|
+
* .action({ name: 'list', handler: async () => success([]) });
|
|
16
|
+
*
|
|
17
|
+
* // Standalone
|
|
18
|
+
* const manager = new ApiKeyManager({ keys: ['sk_live_abc123'] });
|
|
19
|
+
* const result = await manager.validate('sk_live_abc123');
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @module @vinkius-core/mcp-fusion-api-key
|
|
23
|
+
* @author Vinkius Labs
|
|
24
|
+
* @license Apache-2.0
|
|
25
|
+
*/
|
|
26
|
+
export { ApiKeyManager } from './ApiKeyManager.js';
|
|
27
|
+
export type { ApiKeyManagerConfig, ApiKeyValidationResult, } from './ApiKeyManager.js';
|
|
28
|
+
export { createApiKeyTool } from './createApiKeyTool.js';
|
|
29
|
+
export type { ApiKeyToolConfig } from './createApiKeyTool.js';
|
|
30
|
+
export { requireApiKey } from './middleware.js';
|
|
31
|
+
export type { RequireApiKeyOptions } from './middleware.js';
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EACR,mBAAmB,EACnB,sBAAsB,GACzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vinkius-core/mcp-fusion-api-key — API Key Validation for MCP Servers
|
|
3
|
+
*
|
|
4
|
+
* Timing-safe API key validation with SHA-256 hashing,
|
|
5
|
+
* async validators, and mcp-fusion middleware integration.
|
|
6
|
+
* Zero external dependencies — uses native Node.js crypto.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { ApiKeyManager, requireApiKey, createApiKeyTool } from '@vinkius-core/mcp-fusion-api-key';
|
|
11
|
+
*
|
|
12
|
+
* // Middleware
|
|
13
|
+
* const projects = createTool('projects')
|
|
14
|
+
* .use(requireApiKey({ keys: ['sk_live_abc123'] }))
|
|
15
|
+
* .action({ name: 'list', handler: async () => success([]) });
|
|
16
|
+
*
|
|
17
|
+
* // Standalone
|
|
18
|
+
* const manager = new ApiKeyManager({ keys: ['sk_live_abc123'] });
|
|
19
|
+
* const result = await manager.validate('sk_live_abc123');
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @module @vinkius-core/mcp-fusion-api-key
|
|
23
|
+
* @author Vinkius Labs
|
|
24
|
+
* @license Apache-2.0
|
|
25
|
+
*/
|
|
26
|
+
export { ApiKeyManager } from './ApiKeyManager.js';
|
|
27
|
+
export { createApiKeyTool } from './createApiKeyTool.js';
|
|
28
|
+
export { requireApiKey } from './middleware.js';
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key Auth Middleware — requireApiKey()
|
|
3
|
+
*
|
|
4
|
+
* mcp-fusion middleware that ensures requests carry a valid API key.
|
|
5
|
+
* Extracts the key, validates it via ApiKeyManager, and rejects
|
|
6
|
+
* invalid/missing keys with self-healing error responses.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { requireApiKey } from '@vinkius-core/mcp-fusion-api-key';
|
|
11
|
+
*
|
|
12
|
+
* const projects = createTool('projects')
|
|
13
|
+
* .use(requireApiKey({ keys: ['sk_live_abc123'] }))
|
|
14
|
+
* .action({ name: 'list', handler: async (ctx) => { ... } });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import type { ToolResponse } from '@vinkius-core/mcp-fusion';
|
|
18
|
+
import type { ApiKeyManagerConfig } from './ApiKeyManager.js';
|
|
19
|
+
export interface RequireApiKeyOptions extends ApiKeyManagerConfig {
|
|
20
|
+
/**
|
|
21
|
+
* Custom function to extract the API key from the context.
|
|
22
|
+
* Default: checks `ctx.apiKey`, `ctx.headers['x-api-key']`,
|
|
23
|
+
* `ctx.headers.authorization` (ApiKey/Bearer prefix).
|
|
24
|
+
*/
|
|
25
|
+
readonly extractKey?: (ctx: unknown) => string | null | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Callback invoked after successful validation.
|
|
28
|
+
* Use this to inject key metadata into the context.
|
|
29
|
+
*/
|
|
30
|
+
readonly onValidated?: (ctx: unknown, metadata?: Record<string, unknown>) => void;
|
|
31
|
+
/** Error code for toolError response. Default: 'APIKEY_INVALID' */
|
|
32
|
+
readonly errorCode?: string;
|
|
33
|
+
/** Recovery hint for the LLM. Default: 'Provide a valid API key' */
|
|
34
|
+
readonly recoveryHint?: string;
|
|
35
|
+
/** Recovery action name. Default: 'auth' */
|
|
36
|
+
readonly recoveryAction?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Creates a mcp-fusion middleware that validates API keys.
|
|
40
|
+
*
|
|
41
|
+
* Returns `toolError('APIKEY_INVALID')` with self-healing hints
|
|
42
|
+
* when no valid key is found.
|
|
43
|
+
*/
|
|
44
|
+
export declare function requireApiKey(options: RequireApiKeyOptions): (ctx: unknown, _args: Record<string, unknown>, next: () => Promise<ToolResponse>) => Promise<ToolResponse>;
|
|
45
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAM9D,MAAM,WAAW,oBAAqB,SAAQ,mBAAmB;IAC7D;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAElE;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAElF,mEAAmE;IACnE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B,oEAAoE;IACpE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAE/B,4CAA4C;IAC5C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACpC;AAMD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,IAQzC,KAAK,OAAO,EAAE,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,MAAM,OAAO,CAAC,YAAY,CAAC,KAAG,OAAO,CAAC,YAAY,CAAC,CA2BxH"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key Auth Middleware — requireApiKey()
|
|
3
|
+
*
|
|
4
|
+
* mcp-fusion middleware that ensures requests carry a valid API key.
|
|
5
|
+
* Extracts the key, validates it via ApiKeyManager, and rejects
|
|
6
|
+
* invalid/missing keys with self-healing error responses.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { requireApiKey } from '@vinkius-core/mcp-fusion-api-key';
|
|
11
|
+
*
|
|
12
|
+
* const projects = createTool('projects')
|
|
13
|
+
* .use(requireApiKey({ keys: ['sk_live_abc123'] }))
|
|
14
|
+
* .action({ name: 'list', handler: async (ctx) => { ... } });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import { toolError } from '@vinkius-core/mcp-fusion';
|
|
18
|
+
import { ApiKeyManager } from './ApiKeyManager.js';
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Middleware Factory
|
|
21
|
+
// ============================================================================
|
|
22
|
+
/**
|
|
23
|
+
* Creates a mcp-fusion middleware that validates API keys.
|
|
24
|
+
*
|
|
25
|
+
* Returns `toolError('APIKEY_INVALID')` with self-healing hints
|
|
26
|
+
* when no valid key is found.
|
|
27
|
+
*/
|
|
28
|
+
export function requireApiKey(options) {
|
|
29
|
+
const manager = new ApiKeyManager(options);
|
|
30
|
+
const extractKey = options.extractKey ?? defaultExtractKey;
|
|
31
|
+
const errorCode = options.errorCode ?? 'APIKEY_INVALID';
|
|
32
|
+
const recoveryHint = options.recoveryHint ?? 'Provide a valid API key';
|
|
33
|
+
const recoveryAction = options.recoveryAction ?? 'auth';
|
|
34
|
+
const onValidated = options.onValidated;
|
|
35
|
+
return async (ctx, _args, next) => {
|
|
36
|
+
const raw = extractKey(ctx);
|
|
37
|
+
if (!raw) {
|
|
38
|
+
return toolError(errorCode, {
|
|
39
|
+
message: 'API key authentication required',
|
|
40
|
+
suggestion: recoveryHint,
|
|
41
|
+
availableActions: [recoveryAction],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const result = await manager.validate(raw);
|
|
45
|
+
if (!result.valid) {
|
|
46
|
+
return toolError(errorCode, {
|
|
47
|
+
message: `API key validation failed: ${result.reason}`,
|
|
48
|
+
suggestion: recoveryHint,
|
|
49
|
+
availableActions: [recoveryAction],
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (onValidated) {
|
|
53
|
+
onValidated(ctx, result.metadata);
|
|
54
|
+
}
|
|
55
|
+
return next();
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// Default Key Extractor
|
|
60
|
+
// ============================================================================
|
|
61
|
+
/**
|
|
62
|
+
* Default API key extractor — checks common patterns:
|
|
63
|
+
* - `ctx.apiKey`
|
|
64
|
+
* - `ctx.headers['x-api-key']`
|
|
65
|
+
* - `ctx.headers.authorization` with `ApiKey ` or `Bearer ` prefix
|
|
66
|
+
*/
|
|
67
|
+
function defaultExtractKey(ctx) {
|
|
68
|
+
if (!ctx || typeof ctx !== 'object')
|
|
69
|
+
return null;
|
|
70
|
+
const obj = ctx;
|
|
71
|
+
// Direct apiKey property
|
|
72
|
+
if (typeof obj['apiKey'] === 'string' && obj['apiKey']) {
|
|
73
|
+
return obj['apiKey'];
|
|
74
|
+
}
|
|
75
|
+
// Headers
|
|
76
|
+
const headers = obj['headers'];
|
|
77
|
+
if (headers) {
|
|
78
|
+
// x-api-key header
|
|
79
|
+
if (typeof headers['x-api-key'] === 'string' && headers['x-api-key']) {
|
|
80
|
+
return headers['x-api-key'];
|
|
81
|
+
}
|
|
82
|
+
// Authorization header with ApiKey or Bearer prefix
|
|
83
|
+
const auth = headers['authorization'];
|
|
84
|
+
if (typeof auth === 'string' && auth) {
|
|
85
|
+
if (auth.startsWith('ApiKey '))
|
|
86
|
+
return auth.slice(7);
|
|
87
|
+
if (auth.startsWith('Bearer '))
|
|
88
|
+
return auth.slice(7);
|
|
89
|
+
return auth;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vinkius-core/mcp-fusion-api-key",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "API key validation middleware for MCP servers built with mcp-fusion. Timing-safe comparison, SHA-256 hashing, async validators, and self-healing error responses.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"mcp-fusion",
|
|
22
|
+
"api-key",
|
|
23
|
+
"authentication",
|
|
24
|
+
"middleware",
|
|
25
|
+
"ai",
|
|
26
|
+
"llm"
|
|
27
|
+
],
|
|
28
|
+
"author": "Vinkius Labs",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/vinkius-labs/mcp-fusion.git",
|
|
32
|
+
"directory": "packages/api-key"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/vinkius-labs/mcp-fusion/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://mcp-fusion.vinkius.com/",
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"README.md"
|
|
41
|
+
],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@vinkius-core/mcp-fusion": "^2.0.0"
|
|
50
|
+
},
|
|
51
|
+
"license": "Apache-2.0"
|
|
52
|
+
}
|