nitrostack 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/CHANGELOG.md +227 -0
- package/CONTRIBUTING.md +182 -0
- package/LICENSE +201 -0
- package/LICENSE_URLS_UPDATE_COMPLETE.md +388 -0
- package/NOTICE +153 -0
- package/README.md +571 -0
- package/dist/auth/api-key.d.ts +118 -0
- package/dist/auth/api-key.d.ts.map +1 -0
- package/dist/auth/api-key.js +168 -0
- package/dist/auth/api-key.js.map +1 -0
- package/dist/auth/client.d.ts +151 -0
- package/dist/auth/client.d.ts.map +1 -0
- package/dist/auth/client.js +330 -0
- package/dist/auth/client.js.map +1 -0
- package/dist/auth/index.d.ts +30 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +43 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +95 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +260 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/pkce.d.ts +53 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/pkce.js +105 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/quick-setup.d.ts +94 -0
- package/dist/auth/quick-setup.d.ts.map +1 -0
- package/dist/auth/quick-setup.js +210 -0
- package/dist/auth/quick-setup.js.map +1 -0
- package/dist/auth/server-integration.d.ts +97 -0
- package/dist/auth/server-integration.d.ts.map +1 -0
- package/dist/auth/server-integration.js +182 -0
- package/dist/auth/server-integration.js.map +1 -0
- package/dist/auth/server-metadata.d.ts +51 -0
- package/dist/auth/server-metadata.d.ts.map +1 -0
- package/dist/auth/server-metadata.js +106 -0
- package/dist/auth/server-metadata.js.map +1 -0
- package/dist/auth/simple-jwt.d.ts +88 -0
- package/dist/auth/simple-jwt.d.ts.map +1 -0
- package/dist/auth/simple-jwt.js +152 -0
- package/dist/auth/simple-jwt.js.map +1 -0
- package/dist/auth/token-store.d.ts +104 -0
- package/dist/auth/token-store.d.ts.map +1 -0
- package/dist/auth/token-store.js +205 -0
- package/dist/auth/token-store.js.map +1 -0
- package/dist/auth/token-validation.d.ts +47 -0
- package/dist/auth/token-validation.d.ts.map +1 -0
- package/dist/auth/token-validation.js +237 -0
- package/dist/auth/token-validation.js.map +1 -0
- package/dist/auth/types.d.ts +215 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +6 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cli/commands/build.d.ts +6 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +104 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +7 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +312 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/generate-types.d.ts +8 -0
- package/dist/cli/commands/generate-types.d.ts.map +1 -0
- package/dist/cli/commands/generate-types.js +220 -0
- package/dist/cli/commands/generate-types.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +5 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +365 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/init.d.ts +7 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +365 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/start.d.ts +6 -0
- package/dist/cli/commands/start.d.ts.map +1 -0
- package/dist/cli/commands/start.js +61 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +47 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/mcp-dev-wrapper.d.ts +3 -0
- package/dist/cli/mcp-dev-wrapper.d.ts.map +1 -0
- package/dist/cli/mcp-dev-wrapper.js +116 -0
- package/dist/cli/mcp-dev-wrapper.js.map +1 -0
- package/dist/core/apikey-module.d.ts +69 -0
- package/dist/core/apikey-module.d.ts.map +1 -0
- package/dist/core/apikey-module.js +114 -0
- package/dist/core/apikey-module.js.map +1 -0
- package/dist/core/app-decorator.d.ts +58 -0
- package/dist/core/app-decorator.d.ts.map +1 -0
- package/dist/core/app-decorator.js +261 -0
- package/dist/core/app-decorator.js.map +1 -0
- package/dist/core/builders.d.ts +38 -0
- package/dist/core/builders.d.ts.map +1 -0
- package/dist/core/builders.js +129 -0
- package/dist/core/builders.js.map +1 -0
- package/dist/core/component.d.ts +105 -0
- package/dist/core/component.d.ts.map +1 -0
- package/dist/core/component.js +182 -0
- package/dist/core/component.js.map +1 -0
- package/dist/core/config-module.d.ts +55 -0
- package/dist/core/config-module.d.ts.map +1 -0
- package/dist/core/config-module.js +94 -0
- package/dist/core/config-module.js.map +1 -0
- package/dist/core/decorators/cache.decorator.d.ts +61 -0
- package/dist/core/decorators/cache.decorator.d.ts.map +1 -0
- package/dist/core/decorators/cache.decorator.js +115 -0
- package/dist/core/decorators/cache.decorator.js.map +1 -0
- package/dist/core/decorators/health-check.decorator.d.ts +80 -0
- package/dist/core/decorators/health-check.decorator.d.ts.map +1 -0
- package/dist/core/decorators/health-check.decorator.js +153 -0
- package/dist/core/decorators/health-check.decorator.js.map +1 -0
- package/dist/core/decorators/rate-limit.decorator.d.ts +62 -0
- package/dist/core/decorators/rate-limit.decorator.d.ts.map +1 -0
- package/dist/core/decorators/rate-limit.decorator.js +129 -0
- package/dist/core/decorators/rate-limit.decorator.js.map +1 -0
- package/dist/core/decorators.d.ts +151 -0
- package/dist/core/decorators.d.ts.map +1 -0
- package/dist/core/decorators.js +142 -0
- package/dist/core/decorators.js.map +1 -0
- package/dist/core/di/container.d.ts +42 -0
- package/dist/core/di/container.d.ts.map +1 -0
- package/dist/core/di/container.js +76 -0
- package/dist/core/di/container.js.map +1 -0
- package/dist/core/di/injectable.decorator.d.ts +35 -0
- package/dist/core/di/injectable.decorator.d.ts.map +1 -0
- package/dist/core/di/injectable.decorator.js +57 -0
- package/dist/core/di/injectable.decorator.js.map +1 -0
- package/dist/core/errors.d.ts +54 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +87 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/events/event-emitter.d.ts +50 -0
- package/dist/core/events/event-emitter.d.ts.map +1 -0
- package/dist/core/events/event-emitter.js +94 -0
- package/dist/core/events/event-emitter.js.map +1 -0
- package/dist/core/events/event.decorator.d.ts +48 -0
- package/dist/core/events/event.decorator.d.ts.map +1 -0
- package/dist/core/events/event.decorator.js +68 -0
- package/dist/core/events/event.decorator.js.map +1 -0
- package/dist/core/filters/exception-filter.decorator.d.ts +40 -0
- package/dist/core/filters/exception-filter.decorator.d.ts.map +1 -0
- package/dist/core/filters/exception-filter.decorator.js +54 -0
- package/dist/core/filters/exception-filter.decorator.js.map +1 -0
- package/dist/core/filters/exception-filter.interface.d.ts +30 -0
- package/dist/core/filters/exception-filter.interface.d.ts.map +1 -0
- package/dist/core/filters/exception-filter.interface.js +2 -0
- package/dist/core/filters/exception-filter.interface.js.map +1 -0
- package/dist/core/guards/apikey.guard.d.ts +22 -0
- package/dist/core/guards/apikey.guard.d.ts.map +1 -0
- package/dist/core/guards/apikey.guard.js +11 -0
- package/dist/core/guards/apikey.guard.js.map +1 -0
- package/dist/core/guards/guard.interface.d.ts +18 -0
- package/dist/core/guards/guard.interface.d.ts.map +1 -0
- package/dist/core/guards/guard.interface.js +2 -0
- package/dist/core/guards/guard.interface.js.map +1 -0
- package/dist/core/guards/jwt.guard.d.ts +18 -0
- package/dist/core/guards/jwt.guard.d.ts.map +1 -0
- package/dist/core/guards/jwt.guard.js +2 -0
- package/dist/core/guards/jwt.guard.js.map +1 -0
- package/dist/core/guards/oauth.guard.d.ts +35 -0
- package/dist/core/guards/oauth.guard.d.ts.map +1 -0
- package/dist/core/guards/oauth.guard.js +2 -0
- package/dist/core/guards/oauth.guard.js.map +1 -0
- package/dist/core/guards/use-guards.decorator.d.ts +25 -0
- package/dist/core/guards/use-guards.decorator.d.ts.map +1 -0
- package/dist/core/guards/use-guards.decorator.js +32 -0
- package/dist/core/guards/use-guards.decorator.js.map +1 -0
- package/dist/core/health/health-checks.resource.d.ts +14 -0
- package/dist/core/health/health-checks.resource.d.ts.map +1 -0
- package/dist/core/health/health-checks.resource.js +29 -0
- package/dist/core/health/health-checks.resource.js.map +1 -0
- package/dist/core/index.d.ts +55 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +57 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/interceptors/interceptor.decorator.d.ts +37 -0
- package/dist/core/interceptors/interceptor.decorator.d.ts.map +1 -0
- package/dist/core/interceptors/interceptor.decorator.js +51 -0
- package/dist/core/interceptors/interceptor.decorator.js.map +1 -0
- package/dist/core/interceptors/interceptor.interface.d.ts +31 -0
- package/dist/core/interceptors/interceptor.interface.d.ts.map +1 -0
- package/dist/core/interceptors/interceptor.interface.js +2 -0
- package/dist/core/interceptors/interceptor.interface.js.map +1 -0
- package/dist/core/jwt-module.d.ts +51 -0
- package/dist/core/jwt-module.d.ts.map +1 -0
- package/dist/core/jwt-module.js +52 -0
- package/dist/core/jwt-module.js.map +1 -0
- package/dist/core/logger.d.ts +18 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +51 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/middleware/middleware.decorator.d.ts +39 -0
- package/dist/core/middleware/middleware.decorator.d.ts.map +1 -0
- package/dist/core/middleware/middleware.decorator.js +53 -0
- package/dist/core/middleware/middleware.decorator.js.map +1 -0
- package/dist/core/middleware/middleware.interface.d.ts +29 -0
- package/dist/core/middleware/middleware.interface.d.ts.map +1 -0
- package/dist/core/middleware/middleware.interface.js +2 -0
- package/dist/core/middleware/middleware.interface.js.map +1 -0
- package/dist/core/module.d.ts +74 -0
- package/dist/core/module.d.ts.map +1 -0
- package/dist/core/module.js +82 -0
- package/dist/core/module.js.map +1 -0
- package/dist/core/oauth-module.d.ts +144 -0
- package/dist/core/oauth-module.d.ts.map +1 -0
- package/dist/core/oauth-module.js +190 -0
- package/dist/core/oauth-module.js.map +1 -0
- package/dist/core/pipes/pipe.decorator.d.ts +55 -0
- package/dist/core/pipes/pipe.decorator.d.ts.map +1 -0
- package/dist/core/pipes/pipe.decorator.js +85 -0
- package/dist/core/pipes/pipe.decorator.js.map +1 -0
- package/dist/core/pipes/pipe.interface.d.ts +36 -0
- package/dist/core/pipes/pipe.interface.d.ts.map +1 -0
- package/dist/core/pipes/pipe.interface.js +2 -0
- package/dist/core/pipes/pipe.interface.js.map +1 -0
- package/dist/core/prompt.d.ts +37 -0
- package/dist/core/prompt.d.ts.map +1 -0
- package/dist/core/prompt.js +76 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/resource.d.ts +42 -0
- package/dist/core/resource.d.ts.map +1 -0
- package/dist/core/resource.js +90 -0
- package/dist/core/resource.js.map +1 -0
- package/dist/core/server.d.ts +72 -0
- package/dist/core/server.d.ts.map +1 -0
- package/dist/core/server.js +406 -0
- package/dist/core/server.js.map +1 -0
- package/dist/core/tool.d.ts +78 -0
- package/dist/core/tool.d.ts.map +1 -0
- package/dist/core/tool.js +190 -0
- package/dist/core/tool.js.map +1 -0
- package/dist/core/transports/http-server.d.ts +102 -0
- package/dist/core/transports/http-server.d.ts.map +1 -0
- package/dist/core/transports/http-server.js +265 -0
- package/dist/core/transports/http-server.js.map +1 -0
- package/dist/core/types.d.ts +123 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/widgets/widget-examples.resource.d.ts +17 -0
- package/dist/core/widgets/widget-examples.resource.d.ts.map +1 -0
- package/dist/core/widgets/widget-examples.resource.js +28 -0
- package/dist/core/widgets/widget-examples.resource.js.map +1 -0
- package/dist/core/widgets/widget-registry.d.ts +56 -0
- package/dist/core/widgets/widget-registry.d.ts.map +1 -0
- package/dist/core/widgets/widget-registry.js +75 -0
- package/dist/core/widgets/widget-registry.js.map +1 -0
- package/dist/testing/index.d.ts +82 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +164 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/ui-next/index.d.ts +31 -0
- package/dist/ui-next/index.d.ts.map +1 -0
- package/dist/ui-next/index.js +687 -0
- package/dist/ui-next/index.js.map +1 -0
- package/dist/widgets/index.d.ts +9 -0
- package/dist/widgets/index.d.ts.map +1 -0
- package/dist/widgets/index.js +9 -0
- package/dist/widgets/index.js.map +1 -0
- package/dist/widgets/metadata.d.ts +53 -0
- package/dist/widgets/metadata.d.ts.map +1 -0
- package/dist/widgets/metadata.js +29 -0
- package/dist/widgets/metadata.js.map +1 -0
- package/dist/widgets/withToolData.d.ts +19 -0
- package/dist/widgets/withToolData.d.ts.map +1 -0
- package/dist/widgets/withToolData.js +240 -0
- package/dist/widgets/withToolData.js.map +1 -0
- package/jest.config.js +21 -0
- package/package.json +108 -0
- package/templates/typescript-auth/AI_AGENT_CLI_REFERENCE.md +702 -0
- package/templates/typescript-auth/AI_AGENT_SDK_REFERENCE.md +1260 -0
- package/templates/typescript-auth/README.md +400 -0
- package/templates/typescript-auth/package.json +44 -0
- package/templates/typescript-auth-api-key/AI_AGENT_CLI_REFERENCE.md +701 -0
- package/templates/typescript-auth-api-key/AI_AGENT_SDK_REFERENCE.md +1260 -0
- package/templates/typescript-auth-api-key/README.md +483 -0
- package/templates/typescript-auth-api-key/package-lock.json +124 -0
- package/templates/typescript-auth-api-key/package.json +29 -0
- package/templates/typescript-oauth/AI_AGENT_CLI_REFERENCE.md +701 -0
- package/templates/typescript-oauth/AI_AGENT_SDK_REFERENCE.md +1260 -0
- package/templates/typescript-oauth/OAUTH_SETUP.md +406 -0
- package/templates/typescript-oauth/README.md +350 -0
- package/templates/typescript-oauth/package.json +30 -0
- package/templates/typescript-starter/AI_AGENT_CLI_REFERENCE.md +701 -0
- package/templates/typescript-starter/AI_AGENT_SDK_REFERENCE.md +1260 -0
- package/templates/typescript-starter/README.md +312 -0
- package/templates/typescript-starter/package.json +32 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protected Resource Metadata (RFC 9728)
|
|
3
|
+
*
|
|
4
|
+
* MCP servers MUST implement this to advertise their authorization servers
|
|
5
|
+
* to MCP clients. This enables automatic discovery of auth configuration.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Create protected resource metadata document
|
|
9
|
+
*
|
|
10
|
+
* @param resourceUri - The URI of this MCP server
|
|
11
|
+
* @param authorizationServers - Array of authorization server issuer URLs
|
|
12
|
+
* @param scopesSupported - Optional: scopes this resource supports
|
|
13
|
+
*/
|
|
14
|
+
export function createProtectedResourceMetadata(resourceUri, authorizationServers, scopesSupported) {
|
|
15
|
+
return {
|
|
16
|
+
resource: resourceUri,
|
|
17
|
+
authorization_servers: authorizationServers,
|
|
18
|
+
scopes_supported: scopesSupported,
|
|
19
|
+
bearer_methods_supported: ['header'], // MCP uses Bearer tokens in headers
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get well-known URI for protected resource metadata
|
|
24
|
+
* Per RFC 9728, can be at:
|
|
25
|
+
* 1. Resource path: /.well-known/oauth-protected-resource{path}
|
|
26
|
+
* 2. Root: /.well-known/oauth-protected-resource
|
|
27
|
+
*/
|
|
28
|
+
export function getWellKnownMetadataUris(resourceUrl) {
|
|
29
|
+
const urls = [];
|
|
30
|
+
// Method 1: At the path of the resource
|
|
31
|
+
if (resourceUrl.pathname && resourceUrl.pathname !== '/') {
|
|
32
|
+
const pathUri = new URL('/.well-known/oauth-protected-resource' + resourceUrl.pathname, resourceUrl.origin);
|
|
33
|
+
urls.push(pathUri.toString());
|
|
34
|
+
}
|
|
35
|
+
// Method 2: At the root
|
|
36
|
+
const rootUri = new URL('/.well-known/oauth-protected-resource', resourceUrl.origin);
|
|
37
|
+
urls.push(rootUri.toString());
|
|
38
|
+
return urls;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generate WWW-Authenticate header value for 401 responses
|
|
42
|
+
* Per RFC 6750 and MCP spec
|
|
43
|
+
*
|
|
44
|
+
* @param resourceMetadataUrl - URL to protected resource metadata
|
|
45
|
+
* @param scope - Optional: required scopes for this request
|
|
46
|
+
* @param error - Optional: error code (invalid_token, insufficient_scope, etc.)
|
|
47
|
+
* @param errorDescription - Optional: human-readable error description
|
|
48
|
+
*/
|
|
49
|
+
export function generateWWWAuthenticateHeader(options) {
|
|
50
|
+
const parts = ['Bearer'];
|
|
51
|
+
if (options.realm) {
|
|
52
|
+
parts.push(`realm="${options.realm}"`);
|
|
53
|
+
}
|
|
54
|
+
if (options.scope) {
|
|
55
|
+
parts.push(`scope="${options.scope}"`);
|
|
56
|
+
}
|
|
57
|
+
if (options.resourceMetadataUrl) {
|
|
58
|
+
parts.push(`resource_metadata="${options.resourceMetadataUrl}"`);
|
|
59
|
+
}
|
|
60
|
+
if (options.error) {
|
|
61
|
+
parts.push(`error="${options.error}"`);
|
|
62
|
+
}
|
|
63
|
+
if (options.errorDescription) {
|
|
64
|
+
parts.push(`error_description="${options.errorDescription}"`);
|
|
65
|
+
}
|
|
66
|
+
return parts.join(', ');
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Parse WWW-Authenticate header
|
|
70
|
+
* Extracts Bearer auth challenge parameters
|
|
71
|
+
*/
|
|
72
|
+
export function parseWWWAuthenticateHeader(headerValue) {
|
|
73
|
+
if (!headerValue)
|
|
74
|
+
return null;
|
|
75
|
+
// Parse Bearer scheme
|
|
76
|
+
const bearerMatch = headerValue.match(/Bearer\s+(.+)/i);
|
|
77
|
+
if (!bearerMatch)
|
|
78
|
+
return null;
|
|
79
|
+
const params = bearerMatch[1];
|
|
80
|
+
const result = { scheme: 'Bearer' };
|
|
81
|
+
// Parse key="value" pairs
|
|
82
|
+
const paramRegex = /(\w+)="([^"]+)"/g;
|
|
83
|
+
let match;
|
|
84
|
+
while ((match = paramRegex.exec(params)) !== null) {
|
|
85
|
+
const [, key, value] = match;
|
|
86
|
+
switch (key) {
|
|
87
|
+
case 'realm':
|
|
88
|
+
result.realm = value;
|
|
89
|
+
break;
|
|
90
|
+
case 'scope':
|
|
91
|
+
result.scope = value;
|
|
92
|
+
break;
|
|
93
|
+
case 'resource_metadata':
|
|
94
|
+
result.resourceMetadata = value;
|
|
95
|
+
break;
|
|
96
|
+
case 'error':
|
|
97
|
+
result.error = value;
|
|
98
|
+
break;
|
|
99
|
+
case 'error_description':
|
|
100
|
+
result.errorDescription = value;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=server-metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-metadata.js","sourceRoot":"","sources":["../../src/auth/server-metadata.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { RequestHandler } from 'express';
|
|
2
|
+
/**
|
|
3
|
+
* Simple JWT Authentication
|
|
4
|
+
*
|
|
5
|
+
* For 70% of use cases where you don't need full OAuth 2.1 complexity.
|
|
6
|
+
* Perfect for internal tools, APIs, and services that manage their own tokens.
|
|
7
|
+
*/
|
|
8
|
+
export interface SimpleJWTConfig {
|
|
9
|
+
/**
|
|
10
|
+
* JWT secret for signing/verification (HS256)
|
|
11
|
+
* Store in environment variable!
|
|
12
|
+
*/
|
|
13
|
+
secret: string;
|
|
14
|
+
/**
|
|
15
|
+
* Expected audience (who the token is for)
|
|
16
|
+
*/
|
|
17
|
+
audience?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Expected issuer (who created the token)
|
|
20
|
+
*/
|
|
21
|
+
issuer?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Custom claims to validate
|
|
24
|
+
*/
|
|
25
|
+
customValidation?: (payload: any) => boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Algorithm (default: HS256)
|
|
28
|
+
*/
|
|
29
|
+
algorithm?: 'HS256' | 'HS384' | 'HS512';
|
|
30
|
+
}
|
|
31
|
+
export interface JWTPayload {
|
|
32
|
+
sub?: string;
|
|
33
|
+
aud?: string | string[];
|
|
34
|
+
iss?: string;
|
|
35
|
+
exp?: number;
|
|
36
|
+
iat?: number;
|
|
37
|
+
[key: string]: any;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create Simple JWT authentication middleware
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const server = createServer({...});
|
|
45
|
+
*
|
|
46
|
+
* // Simple JWT auth (no OAuth complexity!)
|
|
47
|
+
* server.app.use('/mcp', createSimpleJWTAuth({
|
|
48
|
+
* secret: process.env.JWT_SECRET!,
|
|
49
|
+
* audience: 'my-mcp-server',
|
|
50
|
+
* issuer: 'my-app',
|
|
51
|
+
* }));
|
|
52
|
+
*
|
|
53
|
+
* server.start();
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function createSimpleJWTAuth(config: SimpleJWTConfig): RequestHandler;
|
|
57
|
+
/**
|
|
58
|
+
* Generate a JWT token (helper for testing/development)
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const token = generateJWT({
|
|
63
|
+
* secret: process.env.JWT_SECRET!,
|
|
64
|
+
* payload: {
|
|
65
|
+
* sub: 'user123',
|
|
66
|
+
* scopes: ['mcp:read', 'mcp:write'],
|
|
67
|
+
* },
|
|
68
|
+
* expiresIn: '1h',
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function generateJWT(options: {
|
|
73
|
+
secret: string;
|
|
74
|
+
payload: JWTPayload;
|
|
75
|
+
expiresIn?: string | number;
|
|
76
|
+
audience?: string;
|
|
77
|
+
issuer?: string;
|
|
78
|
+
algorithm?: 'HS256' | 'HS384' | 'HS512';
|
|
79
|
+
}): string;
|
|
80
|
+
/**
|
|
81
|
+
* Verify a JWT token without middleware (helper)
|
|
82
|
+
*/
|
|
83
|
+
export declare function verifyJWT(token: string, config: SimpleJWTConfig): JWTPayload | null;
|
|
84
|
+
/**
|
|
85
|
+
* Decode JWT without verification (for debugging)
|
|
86
|
+
*/
|
|
87
|
+
export declare function decodeJWT(token: string): JWTPayload | null;
|
|
88
|
+
//# sourceMappingURL=simple-jwt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simple-jwt.d.ts","sourceRoot":"","sources":["../../src/auth/simple-jwt.ts"],"names":[],"mappings":"AACA,OAAO,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAE1E;;;;;GAKG;AAEH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;IAE7C;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACzC;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,cAAc,CA8E3E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,UAAU,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACzC,GAAG,MAAM,CAkBT;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,eAAe,GACtB,UAAU,GAAG,IAAI,CAwBnB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAM1D"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
/**
|
|
3
|
+
* Create Simple JWT authentication middleware
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* const server = createServer({...});
|
|
8
|
+
*
|
|
9
|
+
* // Simple JWT auth (no OAuth complexity!)
|
|
10
|
+
* server.app.use('/mcp', createSimpleJWTAuth({
|
|
11
|
+
* secret: process.env.JWT_SECRET!,
|
|
12
|
+
* audience: 'my-mcp-server',
|
|
13
|
+
* issuer: 'my-app',
|
|
14
|
+
* }));
|
|
15
|
+
*
|
|
16
|
+
* server.start();
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function createSimpleJWTAuth(config) {
|
|
20
|
+
const algorithm = config.algorithm || 'HS256';
|
|
21
|
+
return async (req, res, next) => {
|
|
22
|
+
try {
|
|
23
|
+
// 1. Extract token from Authorization header
|
|
24
|
+
const authHeader = req.headers.authorization;
|
|
25
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
26
|
+
return res.status(401).json({
|
|
27
|
+
error: 'unauthorized',
|
|
28
|
+
message: 'Missing or invalid Authorization header. Use: Authorization: Bearer <token>',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const token = authHeader.substring(7); // Remove 'Bearer '
|
|
32
|
+
// 2. Verify JWT
|
|
33
|
+
const verifyOptions = {
|
|
34
|
+
algorithms: [algorithm],
|
|
35
|
+
};
|
|
36
|
+
if (config.audience) {
|
|
37
|
+
verifyOptions.audience = config.audience;
|
|
38
|
+
}
|
|
39
|
+
if (config.issuer) {
|
|
40
|
+
verifyOptions.issuer = config.issuer;
|
|
41
|
+
}
|
|
42
|
+
const payload = jwt.verify(token, config.secret, verifyOptions);
|
|
43
|
+
// 3. Custom validation
|
|
44
|
+
if (config.customValidation && !config.customValidation(payload)) {
|
|
45
|
+
return res.status(403).json({
|
|
46
|
+
error: 'forbidden',
|
|
47
|
+
message: 'Token validation failed',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// 4. Attach to request
|
|
51
|
+
req.auth = {
|
|
52
|
+
authenticated: true,
|
|
53
|
+
tokenInfo: {
|
|
54
|
+
active: true,
|
|
55
|
+
sub: payload.sub,
|
|
56
|
+
aud: typeof payload.aud === 'string' ? [payload.aud] : payload.aud,
|
|
57
|
+
iss: payload.iss,
|
|
58
|
+
exp: payload.exp,
|
|
59
|
+
iat: payload.iat,
|
|
60
|
+
},
|
|
61
|
+
scopes: payload.scopes || payload.scope?.split(' ') || [],
|
|
62
|
+
clientId: payload.client_id || payload.sub,
|
|
63
|
+
subject: payload.sub,
|
|
64
|
+
};
|
|
65
|
+
next();
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error.name === 'TokenExpiredError') {
|
|
69
|
+
return res.status(401).json({
|
|
70
|
+
error: 'token_expired',
|
|
71
|
+
message: 'JWT has expired',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (error.name === 'JsonWebTokenError') {
|
|
75
|
+
return res.status(401).json({
|
|
76
|
+
error: 'invalid_token',
|
|
77
|
+
message: 'Invalid JWT: ' + error.message,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return res.status(500).json({
|
|
81
|
+
error: 'server_error',
|
|
82
|
+
message: 'Token validation failed',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Generate a JWT token (helper for testing/development)
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const token = generateJWT({
|
|
93
|
+
* secret: process.env.JWT_SECRET!,
|
|
94
|
+
* payload: {
|
|
95
|
+
* sub: 'user123',
|
|
96
|
+
* scopes: ['mcp:read', 'mcp:write'],
|
|
97
|
+
* },
|
|
98
|
+
* expiresIn: '1h',
|
|
99
|
+
* });
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export function generateJWT(options) {
|
|
103
|
+
const signOptions = {
|
|
104
|
+
algorithm: options.algorithm || 'HS256',
|
|
105
|
+
};
|
|
106
|
+
if (options.expiresIn !== undefined) {
|
|
107
|
+
signOptions.expiresIn = options.expiresIn; // jwt types are inconsistent
|
|
108
|
+
}
|
|
109
|
+
if (options.audience) {
|
|
110
|
+
signOptions.audience = options.audience;
|
|
111
|
+
}
|
|
112
|
+
if (options.issuer) {
|
|
113
|
+
signOptions.issuer = options.issuer;
|
|
114
|
+
}
|
|
115
|
+
return jwt.sign(options.payload, options.secret, signOptions);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Verify a JWT token without middleware (helper)
|
|
119
|
+
*/
|
|
120
|
+
export function verifyJWT(token, config) {
|
|
121
|
+
try {
|
|
122
|
+
const verifyOptions = {
|
|
123
|
+
algorithms: [config.algorithm || 'HS256'],
|
|
124
|
+
};
|
|
125
|
+
if (config.audience) {
|
|
126
|
+
verifyOptions.audience = config.audience;
|
|
127
|
+
}
|
|
128
|
+
if (config.issuer) {
|
|
129
|
+
verifyOptions.issuer = config.issuer;
|
|
130
|
+
}
|
|
131
|
+
const payload = jwt.verify(token, config.secret, verifyOptions);
|
|
132
|
+
if (config.customValidation && !config.customValidation(payload)) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return payload;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Decode JWT without verification (for debugging)
|
|
143
|
+
*/
|
|
144
|
+
export function decodeJWT(token) {
|
|
145
|
+
try {
|
|
146
|
+
return jwt.decode(token);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=simple-jwt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simple-jwt.js","sourceRoot":"","sources":["../../src/auth/simple-jwt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { StoredToken } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Token Storage Interface
|
|
4
|
+
*
|
|
5
|
+
* Provides secure storage for OAuth tokens
|
|
6
|
+
*/
|
|
7
|
+
export interface TokenStore {
|
|
8
|
+
/**
|
|
9
|
+
* Save a token
|
|
10
|
+
*/
|
|
11
|
+
saveToken(key: string, token: StoredToken): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Get a token
|
|
14
|
+
*/
|
|
15
|
+
getToken(key: string): Promise<StoredToken | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Delete a token
|
|
18
|
+
*/
|
|
19
|
+
deleteToken(key: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* List all stored token keys
|
|
22
|
+
*/
|
|
23
|
+
listKeys(): Promise<string[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Clear all tokens
|
|
26
|
+
*/
|
|
27
|
+
clear(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* In-memory token store
|
|
31
|
+
* For testing and ephemeral storage
|
|
32
|
+
*/
|
|
33
|
+
export declare class MemoryTokenStore implements TokenStore {
|
|
34
|
+
private tokens;
|
|
35
|
+
saveToken(key: string, token: StoredToken): Promise<void>;
|
|
36
|
+
getToken(key: string): Promise<StoredToken | null>;
|
|
37
|
+
deleteToken(key: string): Promise<void>;
|
|
38
|
+
listKeys(): Promise<string[]>;
|
|
39
|
+
clear(): Promise<void>;
|
|
40
|
+
private isTokenExpired;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* File-based token store with encryption
|
|
44
|
+
* For CLI applications and development
|
|
45
|
+
*/
|
|
46
|
+
export declare class FileTokenStore implements TokenStore {
|
|
47
|
+
private storePath;
|
|
48
|
+
private encryptionKey?;
|
|
49
|
+
constructor(storePath: string, encryptionKey?: string);
|
|
50
|
+
saveToken(key: string, token: StoredToken): Promise<void>;
|
|
51
|
+
getToken(key: string): Promise<StoredToken | null>;
|
|
52
|
+
deleteToken(key: string): Promise<void>;
|
|
53
|
+
listKeys(): Promise<string[]>;
|
|
54
|
+
clear(): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Load tokens from file
|
|
57
|
+
*/
|
|
58
|
+
private loadTokens;
|
|
59
|
+
/**
|
|
60
|
+
* Save tokens to file
|
|
61
|
+
*/
|
|
62
|
+
private saveTokens;
|
|
63
|
+
/**
|
|
64
|
+
* Encrypt data using AES-256-GCM
|
|
65
|
+
*/
|
|
66
|
+
private encrypt;
|
|
67
|
+
/**
|
|
68
|
+
* Decrypt data using AES-256-GCM
|
|
69
|
+
*/
|
|
70
|
+
private decrypt;
|
|
71
|
+
/**
|
|
72
|
+
* Derive 256-bit key from password using PBKDF2
|
|
73
|
+
*/
|
|
74
|
+
private deriveKey;
|
|
75
|
+
private isTokenExpired;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Create default token store
|
|
79
|
+
*
|
|
80
|
+
* Uses file-based storage with encryption if password provided
|
|
81
|
+
*
|
|
82
|
+
* @param storePath - Path to store tokens (default: ~/.nitrostack/tokens.json)
|
|
83
|
+
* @param encryptionKey - Optional encryption key
|
|
84
|
+
*/
|
|
85
|
+
export declare function createDefaultTokenStore(storePath?: string, encryptionKey?: string): TokenStore;
|
|
86
|
+
/**
|
|
87
|
+
* Utility: Check if token is expired
|
|
88
|
+
*/
|
|
89
|
+
export declare function isTokenExpired(token: StoredToken): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Utility: Calculate token expiration timestamp
|
|
92
|
+
*/
|
|
93
|
+
export declare function calculateExpiration(expiresIn: number): number;
|
|
94
|
+
/**
|
|
95
|
+
* Utility: Convert TokenResponse to StoredToken
|
|
96
|
+
*/
|
|
97
|
+
export declare function tokenResponseToStored(response: {
|
|
98
|
+
access_token: string;
|
|
99
|
+
token_type: 'Bearer';
|
|
100
|
+
expires_in?: number;
|
|
101
|
+
refresh_token?: string;
|
|
102
|
+
scope?: string;
|
|
103
|
+
}, resource?: string): StoredToken;
|
|
104
|
+
//# sourceMappingURL=token-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1D;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAEnD;;OAEG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC;;OAEG;IACH,QAAQ,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE9B;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;GAGG;AACH,qBAAa,gBAAiB,YAAW,UAAU;IACjD,OAAO,CAAC,MAAM,CAAuC;IAE/C,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAalD,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,QAAQ,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAI7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,OAAO,CAAC,cAAc;CAGvB;AAED;;;GAGG;AACH,qBAAa,cAAe,YAAW,UAAU;IAC/C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAAC,CAAS;gBAEnB,SAAS,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM;IAK/C,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAMzD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAgBlD,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvC,QAAQ,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAK7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;YACW,UAAU;IAoBxB;;OAEG;YACW,UAAU;IAYxB;;OAEG;IACH,OAAO,CAAC,OAAO;IAgBf;;OAEG;IACH,OAAO,CAAC,OAAO;IAuBf;;OAEG;IACH,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,cAAc;CAGvB;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,CAAC,EAAE,MAAM,EAClB,aAAa,CAAC,EAAE,MAAM,GACrB,UAAU,CAQZ;AAUD;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAE1D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,QAAQ,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EACrH,QAAQ,CAAC,EAAE,MAAM,GAChB,WAAW,CASb"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
/**
|
|
5
|
+
* In-memory token store
|
|
6
|
+
* For testing and ephemeral storage
|
|
7
|
+
*/
|
|
8
|
+
export class MemoryTokenStore {
|
|
9
|
+
tokens = new Map();
|
|
10
|
+
async saveToken(key, token) {
|
|
11
|
+
this.tokens.set(key, token);
|
|
12
|
+
}
|
|
13
|
+
async getToken(key) {
|
|
14
|
+
const token = this.tokens.get(key);
|
|
15
|
+
if (!token)
|
|
16
|
+
return null;
|
|
17
|
+
// Check if expired
|
|
18
|
+
if (this.isTokenExpired(token)) {
|
|
19
|
+
this.tokens.delete(key);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return token;
|
|
23
|
+
}
|
|
24
|
+
async deleteToken(key) {
|
|
25
|
+
this.tokens.delete(key);
|
|
26
|
+
}
|
|
27
|
+
async listKeys() {
|
|
28
|
+
return Array.from(this.tokens.keys());
|
|
29
|
+
}
|
|
30
|
+
async clear() {
|
|
31
|
+
this.tokens.clear();
|
|
32
|
+
}
|
|
33
|
+
isTokenExpired(token) {
|
|
34
|
+
return Date.now() > token.expires_at;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* File-based token store with encryption
|
|
39
|
+
* For CLI applications and development
|
|
40
|
+
*/
|
|
41
|
+
export class FileTokenStore {
|
|
42
|
+
storePath;
|
|
43
|
+
encryptionKey;
|
|
44
|
+
constructor(storePath, encryptionKey) {
|
|
45
|
+
this.storePath = storePath;
|
|
46
|
+
this.encryptionKey = encryptionKey;
|
|
47
|
+
}
|
|
48
|
+
async saveToken(key, token) {
|
|
49
|
+
const tokens = await this.loadTokens();
|
|
50
|
+
tokens[key] = token;
|
|
51
|
+
await this.saveTokens(tokens);
|
|
52
|
+
}
|
|
53
|
+
async getToken(key) {
|
|
54
|
+
const tokens = await this.loadTokens();
|
|
55
|
+
const token = tokens[key];
|
|
56
|
+
if (!token)
|
|
57
|
+
return null;
|
|
58
|
+
// Check if expired
|
|
59
|
+
if (this.isTokenExpired(token)) {
|
|
60
|
+
delete tokens[key];
|
|
61
|
+
await this.saveTokens(tokens);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return token;
|
|
65
|
+
}
|
|
66
|
+
async deleteToken(key) {
|
|
67
|
+
const tokens = await this.loadTokens();
|
|
68
|
+
delete tokens[key];
|
|
69
|
+
await this.saveTokens(tokens);
|
|
70
|
+
}
|
|
71
|
+
async listKeys() {
|
|
72
|
+
const tokens = await this.loadTokens();
|
|
73
|
+
return Object.keys(tokens);
|
|
74
|
+
}
|
|
75
|
+
async clear() {
|
|
76
|
+
await this.saveTokens({});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Load tokens from file
|
|
80
|
+
*/
|
|
81
|
+
async loadTokens() {
|
|
82
|
+
try {
|
|
83
|
+
// Ensure directory exists
|
|
84
|
+
await fs.mkdir(path.dirname(this.storePath), { recursive: true });
|
|
85
|
+
// Read file
|
|
86
|
+
const data = await fs.readFile(this.storePath, 'utf-8');
|
|
87
|
+
// Decrypt if encryption is enabled
|
|
88
|
+
const content = this.encryptionKey ? this.decrypt(data) : data;
|
|
89
|
+
return JSON.parse(content);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (error.code === 'ENOENT') {
|
|
93
|
+
return {}; // File doesn't exist yet
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Save tokens to file
|
|
100
|
+
*/
|
|
101
|
+
async saveTokens(tokens) {
|
|
102
|
+
const content = JSON.stringify(tokens, null, 2);
|
|
103
|
+
// Encrypt if encryption is enabled
|
|
104
|
+
const data = this.encryptionKey ? this.encrypt(content) : content;
|
|
105
|
+
// Write to file with restricted permissions
|
|
106
|
+
await fs.writeFile(this.storePath, data, {
|
|
107
|
+
mode: 0o600, // Read/write for owner only
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Encrypt data using AES-256-GCM
|
|
112
|
+
*/
|
|
113
|
+
encrypt(data) {
|
|
114
|
+
if (!this.encryptionKey)
|
|
115
|
+
return data;
|
|
116
|
+
const key = this.deriveKey(this.encryptionKey);
|
|
117
|
+
const iv = crypto.randomBytes(16);
|
|
118
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
119
|
+
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
120
|
+
encrypted += cipher.final('hex');
|
|
121
|
+
const authTag = cipher.getAuthTag();
|
|
122
|
+
// Return: iv:authTag:encrypted
|
|
123
|
+
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Decrypt data using AES-256-GCM
|
|
127
|
+
*/
|
|
128
|
+
decrypt(data) {
|
|
129
|
+
if (!this.encryptionKey)
|
|
130
|
+
return data;
|
|
131
|
+
const key = this.deriveKey(this.encryptionKey);
|
|
132
|
+
const parts = data.split(':');
|
|
133
|
+
if (parts.length !== 3) {
|
|
134
|
+
throw new Error('Invalid encrypted data format');
|
|
135
|
+
}
|
|
136
|
+
const [ivHex, authTagHex, encrypted] = parts;
|
|
137
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
138
|
+
const authTag = Buffer.from(authTagHex, 'hex');
|
|
139
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
|
140
|
+
decipher.setAuthTag(authTag);
|
|
141
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
142
|
+
decrypted += decipher.final('utf8');
|
|
143
|
+
return decrypted;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Derive 256-bit key from password using PBKDF2
|
|
147
|
+
*/
|
|
148
|
+
deriveKey(password) {
|
|
149
|
+
// Use a fixed salt for key derivation
|
|
150
|
+
// In production, consider using a per-user salt
|
|
151
|
+
const salt = 'nitrostack-token-store';
|
|
152
|
+
return crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
|
|
153
|
+
}
|
|
154
|
+
isTokenExpired(token) {
|
|
155
|
+
return Date.now() > token.expires_at;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Create default token store
|
|
160
|
+
*
|
|
161
|
+
* Uses file-based storage with encryption if password provided
|
|
162
|
+
*
|
|
163
|
+
* @param storePath - Path to store tokens (default: ~/.nitrostack/tokens.json)
|
|
164
|
+
* @param encryptionKey - Optional encryption key
|
|
165
|
+
*/
|
|
166
|
+
export function createDefaultTokenStore(storePath, encryptionKey) {
|
|
167
|
+
const defaultPath = storePath || getDefaultStorePath();
|
|
168
|
+
if (encryptionKey) {
|
|
169
|
+
return new FileTokenStore(defaultPath, encryptionKey);
|
|
170
|
+
}
|
|
171
|
+
return new FileTokenStore(defaultPath);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get default token store path
|
|
175
|
+
*/
|
|
176
|
+
function getDefaultStorePath() {
|
|
177
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
178
|
+
return path.join(home, '.nitrostack', 'tokens.json');
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Utility: Check if token is expired
|
|
182
|
+
*/
|
|
183
|
+
export function isTokenExpired(token) {
|
|
184
|
+
return Date.now() > token.expires_at;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Utility: Calculate token expiration timestamp
|
|
188
|
+
*/
|
|
189
|
+
export function calculateExpiration(expiresIn) {
|
|
190
|
+
return Date.now() + expiresIn * 1000;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Utility: Convert TokenResponse to StoredToken
|
|
194
|
+
*/
|
|
195
|
+
export function tokenResponseToStored(response, resource) {
|
|
196
|
+
return {
|
|
197
|
+
access_token: response.access_token,
|
|
198
|
+
token_type: response.token_type,
|
|
199
|
+
expires_at: response.expires_in ? calculateExpiration(response.expires_in) : Date.now() + 3600000, // Default 1 hour
|
|
200
|
+
refresh_token: response.refresh_token,
|
|
201
|
+
scope: response.scope,
|
|
202
|
+
resource,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=token-store.js.map
|