@benlampson/heimdall-server 0.0.0-stage → 5.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -3
- package/dist/client.d.ts +31 -0
- package/dist/client.js +110 -0
- package/dist/client.js.map +1 -0
- package/dist/directory.d.ts +58 -0
- package/dist/directory.js +86 -0
- package/dist/directory.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +10 -0
- package/dist/mcp.js +25 -0
- package/dist/mcp.js.map +1 -0
- package/package.json +36 -6
package/README.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @benlampson/heimdall-server
|
|
2
|
+
|
|
3
|
+
Node 22+ 的 Heimdall 协议与只读目录客户端,版本跟随 Heimdall 整体 Release。支持 public/confidential 授权码、Device Code、服务 Token、刷新、JWT 校验、opaque Token、UserInfo、撤销/内省、退出地址和 MCP 认证头。没有 HTTP 路由、Cookie、UI 或数据库。
|
|
4
|
+
|
|
5
|
+
机密凭据由调用方的 credentialProvider 提供,不能将此入口打包到浏览器。完整示例与接口见仓库 sdk/README.md 和 sdk/API.md。
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { HeimdallServerClient, HeimdallDirectoryClient } from '@benlampson/heimdall-server';
|
|
9
|
+
const server = new HeimdallServerClient({
|
|
10
|
+
authority: 'https://idp.example.com/tenant-a', clientId: 'directory-service',
|
|
11
|
+
audience: 'heimdall-directory-api', scope: 'heimdall.directory.read',
|
|
12
|
+
tenantId: 'tenant-a', applicationId: 'business-app', authenticationMethod: 'client_secret_basic',
|
|
13
|
+
credentialProvider: async () => ({ clientId: 'directory-service', secret: readSecret(), version: readSecretVersion() }),
|
|
14
|
+
});
|
|
15
|
+
const tokens = await server.getClientCredentialsToken();
|
|
16
|
+
const directory = new HeimdallDirectoryClient(server, 'https://api.example.com', 'tenant-a');
|
|
17
|
+
// const permissions = await directory.getUserPermissions(userId);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
服务 Token 可选纯内存缓存,cacheClientCredentials:false 关闭;用户 Token 与登录事务由调用方保存。AK/SK 只提供 Heimdall /mcp 的专用认证头,不转换为 JWT。
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { HeimdallProtocolClient, type HeimdallProtocolOptions, type HeimdallTokenSet } from '@benlampson/heimdall-browser/protocol';
|
|
2
|
+
/** 机密凭据只存在服务端入口;回调支持应用自己的密钥轮换。 */
|
|
3
|
+
export interface HeimdallServerOptions extends HeimdallProtocolOptions {
|
|
4
|
+
authenticationMethod?: 'none' | 'client_secret_basic' | 'client_secret_post';
|
|
5
|
+
credentialProvider?: () => Promise<{
|
|
6
|
+
clientId: string;
|
|
7
|
+
secret: string;
|
|
8
|
+
version: string;
|
|
9
|
+
}>;
|
|
10
|
+
cacheClientCredentials?: boolean;
|
|
11
|
+
resourceBaseUrls?: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
/** Node/服务端纯协议客户端,无默认数据库、Cookie 或 HTTP 路由。 */
|
|
14
|
+
export declare class HeimdallServerClient extends HeimdallProtocolClient {
|
|
15
|
+
private readonly serverOptions;
|
|
16
|
+
private cached?;
|
|
17
|
+
private tokenRequest?;
|
|
18
|
+
constructor(serverOptions: HeimdallServerOptions);
|
|
19
|
+
/** 服务 Token 可选内存缓存;检查当前凭据版本,合并并发换取。 */
|
|
20
|
+
getClientCredentialsToken(signal?: AbortSignal): Promise<HeimdallTokenSet>;
|
|
21
|
+
/** 为允许的资源地址附加调用方的用户 Token 或本客户端服务 Token。 */
|
|
22
|
+
fetch(input: string | URL, init?: RequestInit, accessToken?: string): Promise<Response>;
|
|
23
|
+
/** 只有同机密客户端可内省 Access Token,不能代 public SPA 查询。 */
|
|
24
|
+
introspectToken(token: string, signal?: AbortSignal): Promise<Record<string, unknown>>;
|
|
25
|
+
/** 撤销成功后删除本实例命名服务 Token 缓存。 */
|
|
26
|
+
revokeToken(token: string, hint: 'access_token' | 'refresh_token', signal?: AbortSignal): Promise<void>;
|
|
27
|
+
/** 独立 OAuth Basic 对 client_id/secret 先做表单编码,与 MCP Basic 不混用。 */
|
|
28
|
+
protected authenticate(fields: URLSearchParams, headers: Headers): Promise<void>;
|
|
29
|
+
private applyAuthentication;
|
|
30
|
+
private credentials;
|
|
31
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { HeimdallProtocolClient, HeimdallProtocolError } from '@benlampson/heimdall-browser/protocol';
|
|
2
|
+
/** Node/服务端纯协议客户端,无默认数据库、Cookie 或 HTTP 路由。 */
|
|
3
|
+
export class HeimdallServerClient extends HeimdallProtocolClient {
|
|
4
|
+
serverOptions;
|
|
5
|
+
cached;
|
|
6
|
+
tokenRequest;
|
|
7
|
+
constructor(serverOptions) {
|
|
8
|
+
super(serverOptions);
|
|
9
|
+
this.serverOptions = serverOptions;
|
|
10
|
+
const method = serverOptions.authenticationMethod ?? 'none';
|
|
11
|
+
if (!['none', 'client_secret_basic', 'client_secret_post'].includes(method) || (method !== 'none' && !serverOptions.credentialProvider))
|
|
12
|
+
throw new HeimdallProtocolError('invalid_client_authentication');
|
|
13
|
+
}
|
|
14
|
+
/** 服务 Token 可选内存缓存;检查当前凭据版本,合并并发换取。 */
|
|
15
|
+
async getClientCredentialsToken(signal) {
|
|
16
|
+
if ((this.serverOptions.authenticationMethod ?? 'none') === 'none')
|
|
17
|
+
throw new HeimdallProtocolError('confidential_client_required');
|
|
18
|
+
const credential = await this.credentials();
|
|
19
|
+
if (this.serverOptions.cacheClientCredentials !== false && this.cached?.version === credential.version && this.cached.refreshAt > Date.now())
|
|
20
|
+
return { ...this.cached.tokens };
|
|
21
|
+
if (this.tokenRequest) {
|
|
22
|
+
const pending = this.tokenRequest;
|
|
23
|
+
const result = await pending.promise;
|
|
24
|
+
return pending.version === credential.version ? { ...result } : this.getClientCredentialsToken(signal);
|
|
25
|
+
}
|
|
26
|
+
const promise = (async () => {
|
|
27
|
+
const tokens = await this.requestToken({ grant_type: 'client_credentials', scope: this.options.scope ?? '' }, signal, (fields, headers) => this.applyAuthentication(credential, fields, headers));
|
|
28
|
+
const claims = await this.validateTokens(tokens, 'BackendService');
|
|
29
|
+
const scopes = typeof claims?.scope === 'string' ? parseScopes(claims.scope) : Array.isArray(claims?.scope) ? claims.scope : (tokens.scope ?? '').split(' ');
|
|
30
|
+
if (!(this.options.scope ?? '').split(' ').filter(Boolean).every(scope => scopes.includes(scope)))
|
|
31
|
+
throw new HeimdallProtocolError('token_scope_mismatch');
|
|
32
|
+
const expires = Math.min(tokens.obtained_at + tokens.expires_in * 1000, typeof claims?.exp === 'number' ? claims.exp * 1000 : Infinity);
|
|
33
|
+
const refreshAt = expires - Math.min(30000, (expires - Date.now()) / 2);
|
|
34
|
+
if (this.serverOptions.cacheClientCredentials !== false)
|
|
35
|
+
this.cached = { tokens, version: credential.version, refreshAt };
|
|
36
|
+
return { ...tokens };
|
|
37
|
+
})().finally(() => { this.tokenRequest = undefined; });
|
|
38
|
+
this.tokenRequest = { version: credential.version, promise };
|
|
39
|
+
return promise;
|
|
40
|
+
}
|
|
41
|
+
/** 为允许的资源地址附加调用方的用户 Token 或本客户端服务 Token。 */
|
|
42
|
+
async fetch(input, init = {}, accessToken) {
|
|
43
|
+
const url = this.trustedUrl(String(input));
|
|
44
|
+
if (!(this.serverOptions.resourceBaseUrls ?? []).some(base => {
|
|
45
|
+
const allowed = this.trustedUrl(base);
|
|
46
|
+
const path = allowed.pathname.replace(/\/$/, '');
|
|
47
|
+
return url.origin === allowed.origin && (url.pathname === path || url.pathname.startsWith(path + '/'));
|
|
48
|
+
}))
|
|
49
|
+
throw new HeimdallProtocolError('untrusted_resource');
|
|
50
|
+
const token = accessToken ?? (await this.getClientCredentialsToken(init.signal ?? undefined)).access_token;
|
|
51
|
+
const headers = new Headers(init.headers);
|
|
52
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
53
|
+
return globalThis.fetch(url, { ...init, headers, credentials: 'omit', redirect: 'error',
|
|
54
|
+
signal: init.signal ? AbortSignal.any([init.signal, AbortSignal.timeout(15000)]) : AbortSignal.timeout(15000) });
|
|
55
|
+
}
|
|
56
|
+
/** 只有同机密客户端可内省 Access Token,不能代 public SPA 查询。 */
|
|
57
|
+
async introspectToken(token, signal) {
|
|
58
|
+
if ((this.serverOptions.authenticationMethod ?? 'none') === 'none')
|
|
59
|
+
throw new HeimdallProtocolError('confidential_client_required');
|
|
60
|
+
const value = await this.post(await this.endpoint('introspection_endpoint'), { token }, signal);
|
|
61
|
+
if (typeof value.active !== 'boolean')
|
|
62
|
+
throw new HeimdallProtocolError('invalid_introspection_response');
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
/** 撤销成功后删除本实例命名服务 Token 缓存。 */
|
|
66
|
+
async revokeToken(token, hint, signal) {
|
|
67
|
+
await super.revokeToken(token, hint, signal);
|
|
68
|
+
this.cached = undefined;
|
|
69
|
+
}
|
|
70
|
+
/** 独立 OAuth Basic 对 client_id/secret 先做表单编码,与 MCP Basic 不混用。 */
|
|
71
|
+
async authenticate(fields, headers) {
|
|
72
|
+
const method = this.serverOptions.authenticationMethod ?? 'none';
|
|
73
|
+
if (method === 'none') {
|
|
74
|
+
await super.authenticate(fields, headers);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const credential = await this.credentials();
|
|
78
|
+
await this.applyAuthentication(credential, fields, headers);
|
|
79
|
+
}
|
|
80
|
+
async applyAuthentication(credential, fields, headers) {
|
|
81
|
+
const method = this.serverOptions.authenticationMethod;
|
|
82
|
+
if (method === 'client_secret_basic') {
|
|
83
|
+
const encode = (value) => new URLSearchParams({ v: value }).toString().slice(2);
|
|
84
|
+
headers.set('Authorization', `Basic ${btoa(String.fromCharCode(...new TextEncoder().encode(encode(credential.clientId) + ':' + encode(credential.secret))))}`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
fields.set('client_id', credential.clientId);
|
|
88
|
+
fields.set('client_secret', credential.secret);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async credentials() {
|
|
92
|
+
const value = await this.serverOptions.credentialProvider?.();
|
|
93
|
+
if (!value || value.clientId !== this.options.clientId || !value.secret || !value.version)
|
|
94
|
+
throw new HeimdallProtocolError('invalid_client_credentials');
|
|
95
|
+
return { clientId: value.clientId, secret: value.secret, version: value.version };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Asgard scope 可能使用 JSON 数组字符串,也支持 OAuth 空格范围。 */
|
|
99
|
+
function parseScopes(value) {
|
|
100
|
+
if (!value.trimStart().startsWith('['))
|
|
101
|
+
return value.split(' ');
|
|
102
|
+
try {
|
|
103
|
+
const scopes = JSON.parse(value);
|
|
104
|
+
if (Array.isArray(scopes) && scopes.every(scope => typeof scope === 'string'))
|
|
105
|
+
return scopes;
|
|
106
|
+
}
|
|
107
|
+
catch { /* 统一公开错误。 */ }
|
|
108
|
+
throw new HeimdallProtocolError('invalid_scope_claim');
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { HeimdallServerClient } from './client.js';
|
|
2
|
+
/** 与 Heimdall 后端只读 VO 的字段保持一致;业务数据交由接入方保存。 */
|
|
3
|
+
export interface DirectoryUser {
|
|
4
|
+
id: string;
|
|
5
|
+
tenant_id: string;
|
|
6
|
+
username?: string | null;
|
|
7
|
+
display_name?: string | null;
|
|
8
|
+
email?: string | null;
|
|
9
|
+
phone?: string | null;
|
|
10
|
+
avatar_url?: string | null;
|
|
11
|
+
status: number;
|
|
12
|
+
updated_at: string;
|
|
13
|
+
}
|
|
14
|
+
export interface DirectoryGroup {
|
|
15
|
+
id: string;
|
|
16
|
+
tenant_id: string;
|
|
17
|
+
code: string;
|
|
18
|
+
name: string;
|
|
19
|
+
status: number;
|
|
20
|
+
member_count: number;
|
|
21
|
+
updated_at: string;
|
|
22
|
+
}
|
|
23
|
+
export interface DirectoryMember {
|
|
24
|
+
tenant_user_id: string;
|
|
25
|
+
directory_group_id: string;
|
|
26
|
+
active: boolean;
|
|
27
|
+
updated_at: string;
|
|
28
|
+
}
|
|
29
|
+
export interface DirectoryUserPermissions {
|
|
30
|
+
tenantUserId: string;
|
|
31
|
+
status: 'Active' | 'Disabled';
|
|
32
|
+
permissions: string[];
|
|
33
|
+
updatedAt?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface HeimdallPage<T> {
|
|
36
|
+
data: T[];
|
|
37
|
+
totalCount: number;
|
|
38
|
+
page: number;
|
|
39
|
+
size: number;
|
|
40
|
+
}
|
|
41
|
+
/** 目录使用独立服务 Client 与应用边界,权限结果不默认缓存。 */
|
|
42
|
+
export declare class HeimdallDirectoryClient {
|
|
43
|
+
private readonly tokens;
|
|
44
|
+
private readonly tenantId;
|
|
45
|
+
private readonly base;
|
|
46
|
+
constructor(tokens: HeimdallServerClient, apiBaseUrl: string, tenantId: string, allowLocalhostHttp?: boolean);
|
|
47
|
+
getUser(userId: string, signal?: AbortSignal): Promise<DirectoryUser>;
|
|
48
|
+
getUserPermissions(userId: string, signal?: AbortSignal): Promise<DirectoryUserPermissions>;
|
|
49
|
+
getGroup(groupId: string, signal?: AbortSignal): Promise<DirectoryGroup>;
|
|
50
|
+
getMembership(groupId: string, userId: string, signal?: AbortSignal): Promise<DirectoryMember>;
|
|
51
|
+
getUsers(page?: number, size?: number, signal?: AbortSignal): Promise<HeimdallPage<DirectoryUser>>;
|
|
52
|
+
getGroups(page?: number, size?: number, signal?: AbortSignal): Promise<HeimdallPage<DirectoryGroup>>;
|
|
53
|
+
getMembers(groupId: string, page?: number, size?: number, signal?: AbortSignal): Promise<HeimdallPage<DirectoryMember>>;
|
|
54
|
+
enumerateUsers(size?: number, signal?: AbortSignal): AsyncGenerator<DirectoryUser>;
|
|
55
|
+
private page;
|
|
56
|
+
private data;
|
|
57
|
+
private send;
|
|
58
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { HeimdallProtocolError } from '@benlampson/heimdall-browser/protocol';
|
|
2
|
+
/** 目录使用独立服务 Client 与应用边界,权限结果不默认缓存。 */
|
|
3
|
+
export class HeimdallDirectoryClient {
|
|
4
|
+
tokens;
|
|
5
|
+
tenantId;
|
|
6
|
+
base;
|
|
7
|
+
constructor(tokens, apiBaseUrl, tenantId, allowLocalhostHttp = false) {
|
|
8
|
+
this.tokens = tokens;
|
|
9
|
+
this.tenantId = tenantId;
|
|
10
|
+
this.base = new URL(apiBaseUrl.replace(/\/$/, '') + '/');
|
|
11
|
+
if (!(this.base.protocol === 'https:' || (allowLocalhostHttp && this.base.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(this.base.hostname)))
|
|
12
|
+
|| this.base.username || this.base.password || this.base.hash || this.base.search || !tenantId)
|
|
13
|
+
throw new HeimdallProtocolError('invalid_directory_configuration');
|
|
14
|
+
}
|
|
15
|
+
async getUser(userId, signal) {
|
|
16
|
+
const value = await this.data('users/' + id(userId), signal);
|
|
17
|
+
if (value.id !== userId || value.tenant_id !== this.tenantId || ![0, 1].includes(value.status))
|
|
18
|
+
throw new HeimdallProtocolError('invalid_user_response');
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
async getUserPermissions(userId, signal) {
|
|
22
|
+
const value = await this.data('users/' + id(userId) + '/permissions', signal);
|
|
23
|
+
if (value.tenantUserId !== userId || !['Active', 'Disabled'].includes(value.status) || !Array.isArray(value.permissions)
|
|
24
|
+
|| value.permissions.some(p => typeof p !== 'string' || !p.trim()) || (value.status === 'Disabled' && value.permissions.length))
|
|
25
|
+
throw new HeimdallProtocolError('invalid_permissions_response');
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
async getGroup(groupId, signal) {
|
|
29
|
+
const value = await this.data('groups/' + id(groupId), signal);
|
|
30
|
+
if (value.id !== groupId || value.tenant_id !== this.tenantId || ![0, 1].includes(value.status))
|
|
31
|
+
throw new HeimdallProtocolError('invalid_group_response');
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
async getMembership(groupId, userId, signal) {
|
|
35
|
+
const value = await this.data('groups/' + id(groupId) + '/members/' + id(userId), signal);
|
|
36
|
+
if (value.tenant_user_id !== userId || value.directory_group_id !== groupId || typeof value.active !== 'boolean')
|
|
37
|
+
throw new HeimdallProtocolError('invalid_member_response');
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
getUsers(page = 1, size = 100, signal) { return this.page('users', page, size, signal); }
|
|
41
|
+
getGroups(page = 1, size = 100, signal) { return this.page('groups', page, size, signal); }
|
|
42
|
+
getMembers(groupId, page = 1, size = 100, signal) { return this.page('groups/' + id(groupId) + '/members', page, size, signal); }
|
|
43
|
+
async *enumerateUsers(size = 100, signal) {
|
|
44
|
+
for (let page = 1;; page++) {
|
|
45
|
+
const result = await this.getUsers(page, size, signal);
|
|
46
|
+
yield* result.data;
|
|
47
|
+
if (!result.data.length || page * result.size >= result.totalCount)
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async page(path, page, size, signal) {
|
|
52
|
+
if (!Number.isSafeInteger(page) || page < 1 || !Number.isSafeInteger(size) || size < 1 || size > 500)
|
|
53
|
+
throw new HeimdallProtocolError('invalid_pagination');
|
|
54
|
+
const value = await this.send(path + `?page=${page}&size=${size}`, signal);
|
|
55
|
+
if (!Array.isArray(value.data) || value.page !== page || value.size !== size || !Number.isSafeInteger(value.totalCount) || value.totalCount < 0 || value.data.length > size)
|
|
56
|
+
throw new HeimdallProtocolError('invalid_page');
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
async data(path, signal) {
|
|
60
|
+
const value = await this.send(path, signal);
|
|
61
|
+
if (!value.data || typeof value.data !== 'object' || Array.isArray(value.data))
|
|
62
|
+
throw new HeimdallProtocolError('invalid_response');
|
|
63
|
+
return value.data;
|
|
64
|
+
}
|
|
65
|
+
async send(path, signal) {
|
|
66
|
+
const token = await this.tokens.getClientCredentialsToken(signal);
|
|
67
|
+
const response = await fetch(new URL('api/backend/directory/' + path, this.base), { headers: { Authorization: `Bearer ${token.access_token}` }, redirect: 'error', credentials: 'omit',
|
|
68
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(15000)]) : AbortSignal.timeout(15000) });
|
|
69
|
+
if (!response.ok)
|
|
70
|
+
throw new HeimdallProtocolError(`http_${response.status}`, response.status);
|
|
71
|
+
let value;
|
|
72
|
+
try {
|
|
73
|
+
value = await response.json();
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
throw new HeimdallProtocolError('invalid_response');
|
|
77
|
+
}
|
|
78
|
+
if (!value || typeof value !== 'object' || value.code !== 200)
|
|
79
|
+
throw new HeimdallProtocolError('resource_error');
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** 拒绝 URL 点段,编码普通标识。 */
|
|
84
|
+
function id(value) { if (!value || value === '.' || value === '..')
|
|
85
|
+
throw new HeimdallProtocolError('invalid_identifier'); return encodeURIComponent(value); }
|
|
86
|
+
//# sourceMappingURL=directory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directory.js","sourceRoot":"","sources":["../src/directory.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { HeimdallServerClient } from './client.js';
|
|
2
|
+
export type { HeimdallServerOptions } from './client.js';
|
|
3
|
+
export { HeimdallDirectoryClient } from './directory.js';
|
|
4
|
+
export type { DirectoryUser, DirectoryGroup, DirectoryMember, DirectoryUserPermissions, HeimdallPage } from './directory.js';
|
|
5
|
+
export { createMcpAuthenticationHeaders } from './mcp.js';
|
|
6
|
+
export { HeimdallProtocolError } from '@benlampson/heimdall-browser/protocol';
|
|
7
|
+
export type { HeimdallProtocolOptions, HeimdallTokenSet, HeimdallLoginRequest, HeimdallLoginOptions, HeimdallDeviceAuthorization, HeimdallDeviceTokenResult, HeimdallJwtValidation } from '@benlampson/heimdall-browser/protocol';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { HeimdallServerClient } from './client.js';
|
|
2
|
+
export { HeimdallDirectoryClient } from './directory.js';
|
|
3
|
+
export { createMcpAuthenticationHeaders } from './mcp.js';
|
|
4
|
+
export { HeimdallProtocolError } from '@benlampson/heimdall-browser/protocol';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,OAAO,EAAE,8BAA8B,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC"}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** 返回 MCP 专用认证头,调用方交给标准 MCP 客户端;不重写 MCP 传输。 */
|
|
2
|
+
export declare function createMcpAuthenticationHeaders(mcpUrl: string, authentication: {
|
|
3
|
+
type: 'ak-sk';
|
|
4
|
+
accessKey: string;
|
|
5
|
+
secretKey: string;
|
|
6
|
+
method?: 'basic' | 'headers';
|
|
7
|
+
} | {
|
|
8
|
+
type: 'bearer';
|
|
9
|
+
accessToken: string;
|
|
10
|
+
}): Headers;
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { HeimdallProtocolError } from '@benlampson/heimdall-browser/protocol';
|
|
2
|
+
/** 返回 MCP 专用认证头,调用方交给标准 MCP 客户端;不重写 MCP 传输。 */
|
|
3
|
+
export function createMcpAuthenticationHeaders(mcpUrl, authentication) {
|
|
4
|
+
const url = new URL(mcpUrl);
|
|
5
|
+
if (url.protocol !== 'https:' || url.pathname !== '/mcp' || url.username || url.password || url.hash || url.search)
|
|
6
|
+
throw new HeimdallProtocolError('invalid_mcp_endpoint');
|
|
7
|
+
const headers = new Headers();
|
|
8
|
+
if (authentication.type === 'bearer') {
|
|
9
|
+
if (!authentication.accessToken || /[\r\n]/.test(authentication.accessToken))
|
|
10
|
+
throw new HeimdallProtocolError('invalid_mcp_credentials');
|
|
11
|
+
headers.set('Authorization', `Bearer ${authentication.accessToken}`);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
if (!/^hmcp_[pt]_[^:\s]+$/.test(authentication.accessKey) || !authentication.secretKey || /[\r\n]/.test(authentication.secretKey))
|
|
15
|
+
throw new HeimdallProtocolError('invalid_mcp_credentials');
|
|
16
|
+
if (authentication.method === 'headers') {
|
|
17
|
+
headers.set('X-Heimdall-Access-Key', authentication.accessKey);
|
|
18
|
+
headers.set('X-Heimdall-Secret-Key', authentication.secretKey);
|
|
19
|
+
}
|
|
20
|
+
else
|
|
21
|
+
headers.set('Authorization', `Basic ${btoa(String.fromCharCode(...new TextEncoder().encode(authentication.accessKey + ':' + authentication.secretKey)))}`);
|
|
22
|
+
}
|
|
23
|
+
return headers;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=mcp.js.map
|
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,+CAA+C;AAC/C,MAAM,UAAU,8BAA8B,CAAC,MAAc,EAAE,cACkE;IAC/H,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,MAAM;QAAE,MAAM,IAAI,qBAAqB,CAAC,sBAAsB,CAAC,CAAC;IAC5K,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAC9B,IAAI,cAAc,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACrC,IAAI,CAAC,cAAc,CAAC,WAAW,IAAI,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC;YAAE,MAAM,IAAI,qBAAqB,CAAC,yBAAyB,CAAC,CAAC;QACzI,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,cAAc,CAAC,WAAW,EAAE,CAAC,CAAC;IACvE,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,IAAI,CAAC,cAAc,CAAC,SAAS,IAAI,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC;YAAE,MAAM,IAAI,qBAAqB,CAAC,yBAAyB,CAAC,CAAC;QAC9L,IAAI,cAAc,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;YAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;QAAC,CAAC;;YACvK,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,SAAS,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,SAAS,GAAG,GAAG,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAClK,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@benlampson/heimdall-server",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@benlampson/heimdall-server",
|
|
3
|
+
"version": "5.5.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Heimdall server OAuth, read-only directory and MCP authentication adapters.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/BenLampson/Asgard.Heimdall.git",
|
|
10
|
+
"directory": "sdk/typescript/packages/server"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -p tsconfig.json"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@benlampson/heimdall-browser": "5.5.3"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=22"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public",
|
|
34
|
+
"tag": "latest"
|
|
35
|
+
}
|
|
36
|
+
}
|