@benlampson/heimdall-browser 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 +31 -3
- package/dist/claims.d.ts +11 -0
- package/dist/claims.js +47 -0
- package/dist/claims.js.map +1 -0
- package/dist/client.d.ts +88 -0
- package/dist/client.js +378 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol-types.d.ts +74 -0
- package/dist/protocol-types.js +12 -0
- package/dist/protocol-types.js.map +1 -0
- package/dist/protocol.d.ts +58 -0
- package/dist/protocol.js +273 -0
- package/dist/protocol.js.map +1 -0
- package/dist/session.d.ts +30 -0
- package/dist/session.js +62 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -6
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** 原始 Token 结果交给调用方保存,不强制任何持久化。 */
|
|
2
|
+
export interface HeimdallTokenSet {
|
|
3
|
+
access_token: string;
|
|
4
|
+
token_type: 'Bearer';
|
|
5
|
+
expires_in: number;
|
|
6
|
+
scope?: string;
|
|
7
|
+
refresh_token?: string;
|
|
8
|
+
id_token?: string;
|
|
9
|
+
obtained_at: number;
|
|
10
|
+
}
|
|
11
|
+
/** 协议配置只描述 public Client,服务端入口另提供机密凭据。 */
|
|
12
|
+
export interface HeimdallProtocolOptions {
|
|
13
|
+
authority: string;
|
|
14
|
+
clientId: string;
|
|
15
|
+
audience: string;
|
|
16
|
+
tenantId?: string;
|
|
17
|
+
applicationId?: string;
|
|
18
|
+
scope?: string;
|
|
19
|
+
redirectUri?: string;
|
|
20
|
+
postLogoutRedirectUri?: string;
|
|
21
|
+
accessTokenFormat?: 'jwt' | 'opaque';
|
|
22
|
+
allowLocalhostHttp?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** 由调用方保管的一次性事务;回调消费与防重放由调用方保证。 */
|
|
25
|
+
export interface HeimdallLoginRequest {
|
|
26
|
+
url: string;
|
|
27
|
+
state: string;
|
|
28
|
+
nonce: string;
|
|
29
|
+
codeVerifier: string;
|
|
30
|
+
authority: string;
|
|
31
|
+
clientId: string;
|
|
32
|
+
redirectUri: string;
|
|
33
|
+
createdAt: number;
|
|
34
|
+
}
|
|
35
|
+
/** 只提供现有授权端点已支持的交互参数。 */
|
|
36
|
+
export interface HeimdallLoginOptions {
|
|
37
|
+
prompt?: 'none' | 'login' | 'consent' | 'select_account';
|
|
38
|
+
maxAge?: number;
|
|
39
|
+
responseMode?: 'query' | 'form_post';
|
|
40
|
+
}
|
|
41
|
+
/** 设备审批地址由调用方展示,SDK 不生成界面或自动批准。 */
|
|
42
|
+
export interface HeimdallDeviceAuthorization {
|
|
43
|
+
device_code: string;
|
|
44
|
+
user_code: string;
|
|
45
|
+
verification_uri: string;
|
|
46
|
+
verification_uri_complete?: string;
|
|
47
|
+
expires_in: number;
|
|
48
|
+
interval: number;
|
|
49
|
+
obtained_at: number;
|
|
50
|
+
authority: string;
|
|
51
|
+
client_id: string;
|
|
52
|
+
}
|
|
53
|
+
/** 单次设备码兑换的中间态;其他 OAuth 错误明确抛出。 */
|
|
54
|
+
export type HeimdallDeviceTokenResult = {
|
|
55
|
+
status: 'pending' | 'slow_down';
|
|
56
|
+
} | {
|
|
57
|
+
status: 'complete';
|
|
58
|
+
tokens: HeimdallTokenSet;
|
|
59
|
+
};
|
|
60
|
+
/** JWT 校验可明确指定 ID Token 或 Access Token 的受众与身份种类。 */
|
|
61
|
+
export interface HeimdallJwtValidation {
|
|
62
|
+
audience?: string;
|
|
63
|
+
tokenType?: 'UserLogin' | 'BackendService';
|
|
64
|
+
idToken?: boolean;
|
|
65
|
+
nonce?: string;
|
|
66
|
+
subjectId?: string;
|
|
67
|
+
sessionId?: string;
|
|
68
|
+
}
|
|
69
|
+
/** 元数据与 OIDC 协议错误保持独立,不暴露响应正文、Token 或 Secret。 */
|
|
70
|
+
export declare class HeimdallProtocolError extends Error {
|
|
71
|
+
readonly code: string;
|
|
72
|
+
readonly status?: number | undefined;
|
|
73
|
+
constructor(code: string, status?: number | undefined);
|
|
74
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** 元数据与 OIDC 协议错误保持独立,不暴露响应正文、Token 或 Secret。 */
|
|
2
|
+
export class HeimdallProtocolError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
status;
|
|
5
|
+
constructor(code, status) {
|
|
6
|
+
super(`Heimdall protocol failed: ${code}.`);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.name = 'HeimdallProtocolError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=protocol-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-types.js","sourceRoot":"","sources":["../src/protocol-types.ts"],"names":[],"mappings":"AA2BA,iDAAiD;AACjD,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAClB;IAA8B;IAA1D,YAA4B,IAAY,EAAkB,MAAe;QAAI,KAAK,CAAC,6BAA6B,IAAI,GAAG,CAAC,CAAC;QAA7F,SAAI,GAAJ,IAAI,CAAQ;QAAkB,WAAM,GAAN,MAAM,CAAS;QAAiD,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IAAC,CAAC;CACjK"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type JWTPayload } from 'jose';
|
|
2
|
+
import { type HeimdallProtocolOptions, type HeimdallTokenSet, type HeimdallLoginRequest, type HeimdallLoginOptions, type HeimdallDeviceAuthorization, type HeimdallDeviceTokenResult, type HeimdallJwtValidation } from './protocol-types.js';
|
|
3
|
+
/** 不依赖 window、路由或存储的协议客户端,浏览器与服务端共享此实现。 */
|
|
4
|
+
export declare class HeimdallProtocolClient {
|
|
5
|
+
protected readonly options: HeimdallProtocolOptions;
|
|
6
|
+
private metadata?;
|
|
7
|
+
private keys?;
|
|
8
|
+
constructor(options: HeimdallProtocolOptions);
|
|
9
|
+
/** 固定信任的发行者,不从待验 Token 推导 Discovery。失败允许下一次显式重试。 */
|
|
10
|
+
getDiscovery(): Promise<Record<string, unknown>>;
|
|
11
|
+
/** 返回公开 JWKS,不公开任何私钥。 */
|
|
12
|
+
getJwks(): Promise<Record<string, unknown>>;
|
|
13
|
+
/** 只解码未验证 claims,调用方不得将其作为授权结果。 */
|
|
14
|
+
decodeJwt(token: string): {
|
|
15
|
+
verified: false;
|
|
16
|
+
claims: JWTPayload;
|
|
17
|
+
};
|
|
18
|
+
/** 验签和基本协议边界;ID Token 与资源 AT 使用不同受众。 */
|
|
19
|
+
validateJwt(token: string, context?: HeimdallJwtValidation): Promise<JWTPayload>;
|
|
20
|
+
/** 返回 URL 和 PKCE/state/nonce 上下文,接入方自行选择跳转与保存方式。 */
|
|
21
|
+
createLoginRequest(login?: HeimdallLoginOptions): Promise<HeimdallLoginRequest>;
|
|
22
|
+
/** 对照保存上下文校验回调参数;接入方原子消费事务,禁止重复调用。 */
|
|
23
|
+
exchangeCode(callback: URLSearchParams, request: HeimdallLoginRequest, signal?: AbortSignal): Promise<HeimdallTokenSet>;
|
|
24
|
+
/** 返回设备验证数据,不打开浏览器、不绘制二维码。 */
|
|
25
|
+
requestDeviceAuthorization(signal?: AbortSignal): Promise<HeimdallDeviceAuthorization>;
|
|
26
|
+
/** 单次兑换保留 pending/slow_down,拒绝其它错误与过期设备事务。 */
|
|
27
|
+
exchangeDeviceCode(device: HeimdallDeviceAuthorization, signal?: AbortSignal): Promise<HeimdallDeviceTokenResult>;
|
|
28
|
+
/** 遵守服务器轮询间隔并可取消,slow_down 后持续增加五秒。 */
|
|
29
|
+
waitForDeviceToken(device: HeimdallDeviceAuthorization, signal?: AbortSignal): Promise<HeimdallTokenSet>;
|
|
30
|
+
/** 刷新结果完整返回;可传已验证身份以约束刷新不能换人。 */
|
|
31
|
+
refreshToken(refreshToken: string, identity?: {
|
|
32
|
+
subjectId: string;
|
|
33
|
+
sessionId?: string;
|
|
34
|
+
}, signal?: AbortSignal): Promise<HeimdallTokenSet>;
|
|
35
|
+
/** UserInfo 核对 sub;opaque 可以通过此接口获得资料,不假造权限 claims。 */
|
|
36
|
+
getUserInfo(accessToken: string, subjectId?: string, signal?: AbortSignal): Promise<Record<string, unknown>>;
|
|
37
|
+
/** public Client 撤销只提交自己的 client_id,机密认证由服务端入口添加。 */
|
|
38
|
+
revokeToken(token: string, tokenTypeHint: 'access_token' | 'refresh_token', signal?: AbortSignal): Promise<void>;
|
|
39
|
+
/** 只生成退出请求,接入方决定清理自己的 Cookie/Token 和是否跳转。 */
|
|
40
|
+
createLogoutRequest(idToken: string): Promise<{
|
|
41
|
+
url: string;
|
|
42
|
+
state: string;
|
|
43
|
+
}>;
|
|
44
|
+
/** 接入方消费退出事务;拒绝重复参数与错误响应。 */
|
|
45
|
+
validateLogoutCallback(callback: URLSearchParams, state: string): void;
|
|
46
|
+
/** MCP 公共资源元数据可匿名读取,OAuth 令牌仍受管理应用边界限制。 */
|
|
47
|
+
getMcpProtectedResourceMetadata(mcpUrl: string): Promise<Record<string, unknown>>;
|
|
48
|
+
protected requestToken(fields: Record<string, string>, signal?: AbortSignal, authentication?: (fields: URLSearchParams, headers: Headers) => Promise<void>): Promise<HeimdallTokenSet>;
|
|
49
|
+
protected validateTokens(tokens: HeimdallTokenSet, tokenType: 'UserLogin' | 'BackendService', knownId?: JWTPayload): Promise<JWTPayload | undefined>;
|
|
50
|
+
protected authenticate(fields: URLSearchParams, _headers: Headers): Promise<void>;
|
|
51
|
+
protected endpoint(key: string): Promise<URL>;
|
|
52
|
+
protected trustedUrl(value: string): URL;
|
|
53
|
+
protected post(url: URL, fields: Record<string, string>, signal?: AbortSignal, empty?: boolean, authentication?: (fields: URLSearchParams, headers: Headers) => Promise<void>): Promise<Record<string, unknown>>;
|
|
54
|
+
protected json(url: URL, init?: RequestInit, empty?: boolean): Promise<Record<string, unknown>>;
|
|
55
|
+
private checkDevice;
|
|
56
|
+
}
|
|
57
|
+
export { HeimdallProtocolError } from './protocol-types.js';
|
|
58
|
+
export type { HeimdallProtocolOptions, HeimdallTokenSet, HeimdallLoginRequest, HeimdallLoginOptions, HeimdallDeviceAuthorization, HeimdallDeviceTokenResult, HeimdallJwtValidation } from './protocol-types.js';
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose';
|
|
2
|
+
import { HeimdallProtocolError } from './protocol-types.js';
|
|
3
|
+
/** 不依赖 window、路由或存储的协议客户端,浏览器与服务端共享此实现。 */
|
|
4
|
+
export class HeimdallProtocolClient {
|
|
5
|
+
options;
|
|
6
|
+
metadata;
|
|
7
|
+
keys;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
this.trustedUrl(options.authority);
|
|
11
|
+
if (!options.clientId || !options.audience || !['jwt', 'opaque'].includes(options.accessTokenFormat ?? 'jwt'))
|
|
12
|
+
throw new HeimdallProtocolError('invalid_configuration');
|
|
13
|
+
for (const url of [options.redirectUri, options.postLogoutRedirectUri])
|
|
14
|
+
if (url)
|
|
15
|
+
this.trustedUrl(url);
|
|
16
|
+
}
|
|
17
|
+
/** 固定信任的发行者,不从待验 Token 推导 Discovery。失败允许下一次显式重试。 */
|
|
18
|
+
async getDiscovery() {
|
|
19
|
+
this.metadata ??= this.json(new URL(this.options.authority.replace(/\/$/, '') + '/.well-known/openid-configuration'))
|
|
20
|
+
.then(value => { if (value.issuer !== this.options.authority)
|
|
21
|
+
throw new HeimdallProtocolError('issuer_mismatch'); return value; })
|
|
22
|
+
.catch(error => { this.metadata = undefined; throw error; });
|
|
23
|
+
return { ...await this.metadata };
|
|
24
|
+
}
|
|
25
|
+
/** 返回公开 JWKS,不公开任何私钥。 */
|
|
26
|
+
async getJwks() { return this.json(await this.endpoint('jwks_uri')); }
|
|
27
|
+
/** 只解码未验证 claims,调用方不得将其作为授权结果。 */
|
|
28
|
+
decodeJwt(token) {
|
|
29
|
+
try {
|
|
30
|
+
return { verified: false, claims: decodeJwt(token) };
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new HeimdallProtocolError('invalid_jwt');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** 验签和基本协议边界;ID Token 与资源 AT 使用不同受众。 */
|
|
37
|
+
async validateJwt(token, context = {}) {
|
|
38
|
+
this.keys ??= createRemoteJWKSet(await this.endpoint('jwks_uri'));
|
|
39
|
+
let claims;
|
|
40
|
+
try {
|
|
41
|
+
({ payload: claims } = await jwtVerify(token, this.keys, { issuer: this.options.authority,
|
|
42
|
+
audience: context.audience ?? (context.idToken ? this.options.clientId : this.options.audience), algorithms: ['RS256'], requiredClaims: ['exp', 'iat', 'sub'] }));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new HeimdallProtocolError('invalid_jwt');
|
|
46
|
+
}
|
|
47
|
+
if (typeof claims.sub !== 'string' || !claims.sub || typeof claims.iat !== 'number' || claims.iat > Math.floor(Date.now() / 1000) + 30)
|
|
48
|
+
throw new HeimdallProtocolError('invalid_token_time');
|
|
49
|
+
if (context.idToken) {
|
|
50
|
+
if ((Array.isArray(claims.aud) && claims.aud.length > 1 && claims.azp !== this.options.clientId)
|
|
51
|
+
|| (claims.azp !== undefined && claims.azp !== this.options.clientId) || (context.nonce !== undefined && claims.nonce !== context.nonce))
|
|
52
|
+
throw new HeimdallProtocolError('invalid_id_token');
|
|
53
|
+
}
|
|
54
|
+
else if (claims.client_id !== this.options.clientId
|
|
55
|
+
|| (this.options.tenantId !== undefined && (claims.tenant_id ?? '') !== this.options.tenantId)
|
|
56
|
+
|| (this.options.applicationId !== undefined && claims.application_id !== this.options.applicationId)
|
|
57
|
+
|| (context.tokenType !== undefined && claims.token_type !== context.tokenType))
|
|
58
|
+
throw new HeimdallProtocolError('token_boundary_mismatch');
|
|
59
|
+
if ((context.subjectId !== undefined && claims.sub !== context.subjectId) || (context.sessionId !== undefined && claims.sid !== context.sessionId))
|
|
60
|
+
throw new HeimdallProtocolError('identity_changed');
|
|
61
|
+
return claims;
|
|
62
|
+
}
|
|
63
|
+
/** 返回 URL 和 PKCE/state/nonce 上下文,接入方自行选择跳转与保存方式。 */
|
|
64
|
+
async createLoginRequest(login = {}) {
|
|
65
|
+
if (!this.options.redirectUri || !(this.options.scope ?? 'openid profile email offline_access').split(' ').includes('openid'))
|
|
66
|
+
throw new HeimdallProtocolError('invalid_login_configuration');
|
|
67
|
+
if (login.maxAge !== undefined && (!Number.isSafeInteger(login.maxAge) || login.maxAge < 0))
|
|
68
|
+
throw new HeimdallProtocolError('invalid_max_age');
|
|
69
|
+
if ((login.prompt !== undefined && !['none', 'login', 'consent', 'select_account'].includes(login.prompt))
|
|
70
|
+
|| (login.responseMode !== undefined && !['query', 'form_post'].includes(login.responseMode)))
|
|
71
|
+
throw new HeimdallProtocolError('invalid_login_parameters');
|
|
72
|
+
const request = { state: random(), nonce: random(), codeVerifier: random(), authority: this.options.authority,
|
|
73
|
+
clientId: this.options.clientId, redirectUri: this.options.redirectUri, createdAt: Date.now() };
|
|
74
|
+
const challenge = base64url(new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(request.codeVerifier))));
|
|
75
|
+
const url = await this.endpoint('authorization_endpoint');
|
|
76
|
+
for (const [key, value] of Object.entries({ client_id: request.clientId, redirect_uri: request.redirectUri, response_type: 'code',
|
|
77
|
+
scope: this.options.scope ?? 'openid profile email offline_access', state: request.state, nonce: request.nonce,
|
|
78
|
+
code_challenge: challenge, code_challenge_method: 'S256', response_mode: login.responseMode ?? 'query' }))
|
|
79
|
+
url.searchParams.set(key, value);
|
|
80
|
+
if (login.prompt)
|
|
81
|
+
url.searchParams.set('prompt', login.prompt);
|
|
82
|
+
if (login.maxAge !== undefined)
|
|
83
|
+
url.searchParams.set('max_age', String(login.maxAge));
|
|
84
|
+
return { ...request, url: url.href };
|
|
85
|
+
}
|
|
86
|
+
/** 对照保存上下文校验回调参数;接入方原子消费事务,禁止重复调用。 */
|
|
87
|
+
async exchangeCode(callback, request, signal) {
|
|
88
|
+
if (request.authority !== this.options.authority || request.clientId !== this.options.clientId || request.redirectUri !== this.options.redirectUri
|
|
89
|
+
|| !request.state || !request.nonce || !/^[A-Za-z0-9_-]{43,128}$/.test(request.codeVerifier)
|
|
90
|
+
|| !Number.isFinite(request.createdAt) || Date.now() - request.createdAt > 600000 || request.createdAt > Date.now() + 30000
|
|
91
|
+
|| callback.getAll('state').length !== 1 || callback.get('state') !== request.state)
|
|
92
|
+
throw new HeimdallProtocolError('invalid_callback_state');
|
|
93
|
+
if (callback.has('error'))
|
|
94
|
+
throw new HeimdallProtocolError(oauthCode(callback.get('error')));
|
|
95
|
+
if (callback.getAll('code').length !== 1 || !callback.get('code'))
|
|
96
|
+
throw new HeimdallProtocolError('invalid_callback');
|
|
97
|
+
const tokens = await this.requestToken({ grant_type: 'authorization_code', code: callback.get('code'), redirect_uri: request.redirectUri, code_verifier: request.codeVerifier }, signal);
|
|
98
|
+
if (!tokens.id_token)
|
|
99
|
+
throw new HeimdallProtocolError('id_token_missing');
|
|
100
|
+
const id = await this.validateJwt(tokens.id_token, { idToken: true, nonce: request.nonce });
|
|
101
|
+
await this.validateTokens(tokens, 'UserLogin', id);
|
|
102
|
+
return tokens;
|
|
103
|
+
}
|
|
104
|
+
/** 返回设备验证数据,不打开浏览器、不绘制二维码。 */
|
|
105
|
+
async requestDeviceAuthorization(signal) {
|
|
106
|
+
const value = await this.post(await this.endpoint('device_authorization_endpoint'), { scope: this.options.scope ?? 'openid profile email offline_access' }, signal);
|
|
107
|
+
if (!text(value.device_code) || !text(value.user_code) || !text(value.verification_uri) || !positive(value.expires_in) || !positive(value.interval))
|
|
108
|
+
throw new HeimdallProtocolError('invalid_device_response');
|
|
109
|
+
this.trustedUrl(value.verification_uri);
|
|
110
|
+
if (value.verification_uri_complete !== undefined)
|
|
111
|
+
this.trustedUrl(value.verification_uri_complete);
|
|
112
|
+
return { ...value, obtained_at: Date.now(), authority: this.options.authority, client_id: this.options.clientId };
|
|
113
|
+
}
|
|
114
|
+
/** 单次兑换保留 pending/slow_down,拒绝其它错误与过期设备事务。 */
|
|
115
|
+
async exchangeDeviceCode(device, signal) {
|
|
116
|
+
this.checkDevice(device);
|
|
117
|
+
try {
|
|
118
|
+
const tokens = await this.requestToken({ grant_type: 'urn:ietf:params:oauth:grant-type:device_code', device_code: device.device_code }, signal);
|
|
119
|
+
await this.validateTokens(tokens, 'UserLogin');
|
|
120
|
+
return { status: 'complete', tokens };
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (error instanceof HeimdallProtocolError && ['authorization_pending', 'slow_down'].includes(error.code))
|
|
124
|
+
return { status: error.code === 'slow_down' ? 'slow_down' : 'pending' };
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** 遵守服务器轮询间隔并可取消,slow_down 后持续增加五秒。 */
|
|
129
|
+
async waitForDeviceToken(device, signal) {
|
|
130
|
+
this.checkDevice(device);
|
|
131
|
+
let interval = device.interval;
|
|
132
|
+
while (true) {
|
|
133
|
+
const remaining = device.obtained_at + device.expires_in * 1000 - Date.now();
|
|
134
|
+
await delay(Math.min(interval * 1000, remaining), signal);
|
|
135
|
+
// 剩余寿命不足下一次合法轮询时只等待过期,提前唤醒的计时器不能触发额外 POST。
|
|
136
|
+
if (remaining <= interval * 1000)
|
|
137
|
+
throw new HeimdallProtocolError('device_expired');
|
|
138
|
+
const result = await this.exchangeDeviceCode(device, signal);
|
|
139
|
+
if (result.status === 'complete')
|
|
140
|
+
return result.tokens;
|
|
141
|
+
if (result.status === 'slow_down')
|
|
142
|
+
interval += 5;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/** 刷新结果完整返回;可传已验证身份以约束刷新不能换人。 */
|
|
146
|
+
async refreshToken(refreshToken, identity, signal) {
|
|
147
|
+
if (!refreshToken)
|
|
148
|
+
throw new HeimdallProtocolError('invalid_refresh_token');
|
|
149
|
+
const tokens = await this.requestToken({ grant_type: 'refresh_token', refresh_token: refreshToken }, signal);
|
|
150
|
+
await this.validateTokens(tokens, 'UserLogin');
|
|
151
|
+
if (identity) {
|
|
152
|
+
const claims = tokens.id_token ? await this.validateJwt(tokens.id_token, { idToken: true, ...identity })
|
|
153
|
+
: (this.options.accessTokenFormat ?? 'jwt') === 'jwt' ? await this.validateJwt(tokens.access_token, { tokenType: 'UserLogin', ...identity }) : undefined;
|
|
154
|
+
if (!claims)
|
|
155
|
+
throw new HeimdallProtocolError('identity_unverifiable');
|
|
156
|
+
}
|
|
157
|
+
return tokens;
|
|
158
|
+
}
|
|
159
|
+
/** UserInfo 核对 sub;opaque 可以通过此接口获得资料,不假造权限 claims。 */
|
|
160
|
+
async getUserInfo(accessToken, subjectId, signal) {
|
|
161
|
+
const profile = await this.json(await this.endpoint('userinfo_endpoint'), { headers: { Authorization: `Bearer ${accessToken}` }, signal });
|
|
162
|
+
if (!text(profile.sub) || (subjectId !== undefined && profile.sub !== subjectId))
|
|
163
|
+
throw new HeimdallProtocolError('identity_changed');
|
|
164
|
+
return profile;
|
|
165
|
+
}
|
|
166
|
+
/** public Client 撤销只提交自己的 client_id,机密认证由服务端入口添加。 */
|
|
167
|
+
async revokeToken(token, tokenTypeHint, signal) {
|
|
168
|
+
if (!token || !['access_token', 'refresh_token'].includes(tokenTypeHint))
|
|
169
|
+
throw new HeimdallProtocolError('invalid_revocation');
|
|
170
|
+
await this.post(await this.endpoint('revocation_endpoint'), { token, token_type_hint: tokenTypeHint }, signal, true);
|
|
171
|
+
}
|
|
172
|
+
/** 只生成退出请求,接入方决定清理自己的 Cookie/Token 和是否跳转。 */
|
|
173
|
+
async createLogoutRequest(idToken) {
|
|
174
|
+
if (!this.options.postLogoutRedirectUri || !idToken)
|
|
175
|
+
throw new HeimdallProtocolError('logout_configuration_missing');
|
|
176
|
+
const state = random();
|
|
177
|
+
const url = await this.endpoint('end_session_endpoint');
|
|
178
|
+
for (const [key, value] of Object.entries({ client_id: this.options.clientId, id_token_hint: idToken,
|
|
179
|
+
post_logout_redirect_uri: this.options.postLogoutRedirectUri, state }))
|
|
180
|
+
url.searchParams.set(key, value);
|
|
181
|
+
return { url: url.href, state };
|
|
182
|
+
}
|
|
183
|
+
/** 接入方消费退出事务;拒绝重复参数与错误响应。 */
|
|
184
|
+
validateLogoutCallback(callback, state) {
|
|
185
|
+
if (!state || callback.getAll('state').length !== 1 || callback.get('state') !== state)
|
|
186
|
+
throw new HeimdallProtocolError('invalid_logout_state');
|
|
187
|
+
if (callback.has('error'))
|
|
188
|
+
throw new HeimdallProtocolError(oauthCode(callback.get('error')));
|
|
189
|
+
}
|
|
190
|
+
/** MCP 公共资源元数据可匿名读取,OAuth 令牌仍受管理应用边界限制。 */
|
|
191
|
+
async getMcpProtectedResourceMetadata(mcpUrl) {
|
|
192
|
+
const url = this.trustedUrl(mcpUrl);
|
|
193
|
+
if (url.pathname !== '/mcp')
|
|
194
|
+
throw new HeimdallProtocolError('invalid_mcp_endpoint');
|
|
195
|
+
return this.json(new URL('/.well-known/oauth-protected-resource/mcp', url));
|
|
196
|
+
}
|
|
197
|
+
async requestToken(fields, signal, authentication) {
|
|
198
|
+
const value = await this.post(await this.endpoint('token_endpoint'), fields, signal, false, authentication);
|
|
199
|
+
if (!text(value.access_token) || typeof value.token_type !== 'string' || value.token_type.toLowerCase() !== 'bearer' || !positive(value.expires_in)
|
|
200
|
+
|| [value.refresh_token, value.id_token, value.scope].some(field => field !== undefined && !text(field)))
|
|
201
|
+
throw new HeimdallProtocolError('invalid_token_response');
|
|
202
|
+
return { ...value, token_type: 'Bearer', obtained_at: Date.now() };
|
|
203
|
+
}
|
|
204
|
+
async validateTokens(tokens, tokenType, knownId) {
|
|
205
|
+
const id = knownId ?? (tokens.id_token ? await this.validateJwt(tokens.id_token, { idToken: true }) : undefined);
|
|
206
|
+
if ((this.options.accessTokenFormat ?? 'jwt') === 'jwt')
|
|
207
|
+
return this.validateJwt(tokens.access_token, { tokenType, subjectId: id?.sub, sessionId: typeof id?.sid === 'string' ? id.sid : undefined });
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
async authenticate(fields, _headers) { fields.set('client_id', this.options.clientId); }
|
|
211
|
+
async endpoint(key) { const value = (await this.getDiscovery())[key]; if (!text(value))
|
|
212
|
+
throw new HeimdallProtocolError('endpoint_missing'); return this.trustedUrl(value); }
|
|
213
|
+
trustedUrl(value) {
|
|
214
|
+
let url;
|
|
215
|
+
try {
|
|
216
|
+
url = new URL(value);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
throw new HeimdallProtocolError('invalid_endpoint');
|
|
220
|
+
}
|
|
221
|
+
if (url.username || url.password || url.hash || !(url.protocol === 'https:' || (this.options.allowLocalhostHttp && url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname))))
|
|
222
|
+
throw new HeimdallProtocolError('untrusted_endpoint');
|
|
223
|
+
return url;
|
|
224
|
+
}
|
|
225
|
+
async post(url, fields, signal, empty = false, authentication) {
|
|
226
|
+
const form = new URLSearchParams(fields);
|
|
227
|
+
const headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
|
|
228
|
+
await (authentication ?? this.authenticate.bind(this))(form, headers);
|
|
229
|
+
return this.json(url, { method: 'POST', body: form, headers, signal }, empty);
|
|
230
|
+
}
|
|
231
|
+
async json(url, init = {}, empty = false) {
|
|
232
|
+
const signal = init.signal ? AbortSignal.any([init.signal, AbortSignal.timeout(15000)]) : AbortSignal.timeout(15000);
|
|
233
|
+
const response = await fetch(url, { ...init, signal, credentials: 'omit', redirect: 'error', cache: 'no-store' });
|
|
234
|
+
if (empty && response.ok)
|
|
235
|
+
return {};
|
|
236
|
+
let value;
|
|
237
|
+
try {
|
|
238
|
+
value = await response.json();
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
throw new HeimdallProtocolError('invalid_response', response.status);
|
|
242
|
+
}
|
|
243
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
244
|
+
throw new HeimdallProtocolError('invalid_response', response.status);
|
|
245
|
+
const record = value;
|
|
246
|
+
if (!response.ok || record.error)
|
|
247
|
+
throw new HeimdallProtocolError(record.error ? oauthCode(record.error) : `http_${response.status}`, response.status);
|
|
248
|
+
return record;
|
|
249
|
+
}
|
|
250
|
+
checkDevice(device) {
|
|
251
|
+
if (device.authority !== this.options.authority || device.client_id !== this.options.clientId || !device.device_code || !positive(device.interval)
|
|
252
|
+
|| !positive(device.expires_in) || !Number.isFinite(device.obtained_at) || device.obtained_at > Date.now() + 30000)
|
|
253
|
+
throw new HeimdallProtocolError('invalid_device_context');
|
|
254
|
+
if (device.obtained_at + device.expires_in * 1000 <= Date.now())
|
|
255
|
+
throw new HeimdallProtocolError('device_expired');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const text = (value) => typeof value === 'string' && value.length > 0;
|
|
259
|
+
const positive = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
|
|
260
|
+
const oauthCode = (value) => typeof value === 'string' && /^[a-z_]{1,64}$/.test(value) ? value : 'oauth_error';
|
|
261
|
+
const base64url = (bytes) => btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
262
|
+
const random = () => base64url(crypto.getRandomValues(new Uint8Array(32)));
|
|
263
|
+
/** 等待设备轮询支持 AbortSignal,完成时移除监听避免泄漏。 */
|
|
264
|
+
async function delay(ms, signal) {
|
|
265
|
+
signal?.throwIfAborted();
|
|
266
|
+
await new Promise((resolve, reject) => {
|
|
267
|
+
const abort = () => { clearTimeout(timer); reject(signal.reason); };
|
|
268
|
+
const timer = setTimeout(() => { signal?.removeEventListener('abort', abort); resolve(); }, Math.max(0, ms));
|
|
269
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
export { HeimdallProtocolError } from './protocol-types.js';
|
|
273
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** 单一记录原子提交版本和凭据,退出留下门闩;独立 app/issuer/client 命名空间。 */
|
|
2
|
+
export interface SessionRecord {
|
|
3
|
+
version: string;
|
|
4
|
+
user?: string;
|
|
5
|
+
}
|
|
6
|
+
/** 本地串行锁与跨标签页锁的包装,存储读取失败直接拒绝。 */
|
|
7
|
+
export declare class SessionStore {
|
|
8
|
+
readonly key: string;
|
|
9
|
+
private readonly storage;
|
|
10
|
+
private readonly shared;
|
|
11
|
+
private queue;
|
|
12
|
+
constructor(key: string, storage: Storage, shared: boolean);
|
|
13
|
+
read(): SessionRecord;
|
|
14
|
+
/** 比较版本后提交;旧回调/刷新永远不能复活退出的会话。 */
|
|
15
|
+
commit(expected: string, user?: string): SessionRecord;
|
|
16
|
+
/** 所有同实例变更串行,shared 模式同时使用同源 Web Locks。 */
|
|
17
|
+
locked<T>(operation: () => Promise<T> | T): Promise<T>;
|
|
18
|
+
/** 协议刷新与回调串行,但退出能立刻写门闩,不等待网络。 */
|
|
19
|
+
protocol<T>(operation: () => Promise<T>): Promise<T>;
|
|
20
|
+
}
|
|
21
|
+
/** 候选管理器的用户存储永不直接覆盖正式应用凭据。 */
|
|
22
|
+
export declare class MemoryStorage implements Storage {
|
|
23
|
+
private readonly values;
|
|
24
|
+
get length(): number;
|
|
25
|
+
clear(): void;
|
|
26
|
+
getItem(key: string): string | null;
|
|
27
|
+
key(index: number): string | null;
|
|
28
|
+
removeItem(key: string): void;
|
|
29
|
+
setItem(key: string, value: string): void;
|
|
30
|
+
}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { HeimdallBrowserError } from './types.js';
|
|
2
|
+
/** 本地串行锁与跨标签页锁的包装,存储读取失败直接拒绝。 */
|
|
3
|
+
export class SessionStore {
|
|
4
|
+
key;
|
|
5
|
+
storage;
|
|
6
|
+
shared;
|
|
7
|
+
queue = Promise.resolve();
|
|
8
|
+
constructor(key, storage, shared) {
|
|
9
|
+
this.key = key;
|
|
10
|
+
this.storage = storage;
|
|
11
|
+
this.shared = shared;
|
|
12
|
+
if (shared && !globalThis.navigator?.locks)
|
|
13
|
+
throw new HeimdallBrowserError('web_locks_required');
|
|
14
|
+
}
|
|
15
|
+
read() {
|
|
16
|
+
const raw = this.storage.getItem(this.key);
|
|
17
|
+
if (!raw)
|
|
18
|
+
return { version: 'empty' };
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(raw);
|
|
21
|
+
if (!parsed || typeof parsed !== 'object')
|
|
22
|
+
throw new Error();
|
|
23
|
+
const value = parsed;
|
|
24
|
+
if (typeof value.version !== 'string' || (value.user !== undefined && typeof value.user !== 'string'))
|
|
25
|
+
throw new Error();
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw new HeimdallBrowserError('invalid_session_storage');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** 比较版本后提交;旧回调/刷新永远不能复活退出的会话。 */
|
|
33
|
+
commit(expected, user) {
|
|
34
|
+
if (this.read().version !== expected)
|
|
35
|
+
throw new HeimdallBrowserError('session_changed');
|
|
36
|
+
const result = { version: crypto.randomUUID(), ...(user ? { user } : {}) };
|
|
37
|
+
this.storage.setItem(this.key, JSON.stringify(result));
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
/** 所有同实例变更串行,shared 模式同时使用同源 Web Locks。 */
|
|
41
|
+
async locked(operation) {
|
|
42
|
+
const run = () => this.shared ? navigator.locks.request(this.key, operation) : Promise.resolve().then(operation);
|
|
43
|
+
const result = this.queue.then(run, run);
|
|
44
|
+
this.queue = result.catch(() => undefined);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
/** 协议刷新与回调串行,但退出能立刻写门闩,不等待网络。 */
|
|
48
|
+
async protocol(operation) {
|
|
49
|
+
return this.shared ? navigator.locks.request(this.key + ':protocol', operation) : operation();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** 候选管理器的用户存储永不直接覆盖正式应用凭据。 */
|
|
53
|
+
export class MemoryStorage {
|
|
54
|
+
values = new Map();
|
|
55
|
+
get length() { return this.values.size; }
|
|
56
|
+
clear() { this.values.clear(); }
|
|
57
|
+
getItem(key) { return this.values.get(key) ?? null; }
|
|
58
|
+
key(index) { return [...this.values.keys()][index] ?? null; }
|
|
59
|
+
removeItem(key) { this.values.delete(key); }
|
|
60
|
+
setItem(key, value) { this.values.set(key, value); }
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** 身份数据与权限声明;资源服务器仍执行最终授权。 */
|
|
2
|
+
export interface HeimdallIdentity {
|
|
3
|
+
issuer: string;
|
|
4
|
+
clientId: string;
|
|
5
|
+
tenantId: string;
|
|
6
|
+
applicationId: string;
|
|
7
|
+
subjectId: string;
|
|
8
|
+
sessionId: string;
|
|
9
|
+
roles: readonly string[];
|
|
10
|
+
permissions: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
/** 登记地址和受信任资源集合,SDK 不接受浏览器 Secret。 */
|
|
13
|
+
export interface HeimdallBrowserOptions {
|
|
14
|
+
authority: string;
|
|
15
|
+
clientId: string;
|
|
16
|
+
tenantId: string;
|
|
17
|
+
applicationId: string;
|
|
18
|
+
audience: string;
|
|
19
|
+
redirectUri: string;
|
|
20
|
+
postLogoutRedirectUri: string;
|
|
21
|
+
resourceBaseUrls?: readonly string[];
|
|
22
|
+
scope?: string;
|
|
23
|
+
accessTokenFormat?: 'jwt' | 'opaque';
|
|
24
|
+
allowLocalhostHttp?: boolean;
|
|
25
|
+
/** 默认 localStorage,自动跨标签页协调,要求 Web Locks;可指定 sessionStorage、自定义 Storage,或 false 仅用内存。 */
|
|
26
|
+
storage?: Storage | false;
|
|
27
|
+
sharedStorage?: boolean;
|
|
28
|
+
/** 自动续期在已登录时启动,退出后绝不自动发起 SSO。 */
|
|
29
|
+
automaticRenew?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** 响应与会话变化使用稳定错误分类,避免记录协议 URL 或 Token。 */
|
|
32
|
+
export declare class HeimdallBrowserError extends Error {
|
|
33
|
+
readonly code: string;
|
|
34
|
+
constructor(code: string);
|
|
35
|
+
}
|
|
36
|
+
/** 应用退出和撤销结果独立,不承诺其他应用已退出。 */
|
|
37
|
+
export interface ApplicationLogoutResult {
|
|
38
|
+
localLogoutCompleted: true;
|
|
39
|
+
revocationConfirmed: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** 身份数据快照不公开 AT/RT,支持会话变化订阅。 */
|
|
42
|
+
export interface HeimdallSnapshot {
|
|
43
|
+
identity: HeimdallIdentity | null;
|
|
44
|
+
version: string;
|
|
45
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** 响应与会话变化使用稳定错误分类,避免记录协议 URL 或 Token。 */
|
|
2
|
+
export class HeimdallBrowserError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(code) {
|
|
5
|
+
super(`Heimdall operation failed: ${code}.`);
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.name = 'HeimdallBrowserError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAkBA,0CAA0C;AAC1C,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IACjB;IAA5B,YAA4B,IAAY;QAAI,KAAK,CAAC,8BAA8B,IAAI,GAAG,CAAC,CAAC;QAA7D,SAAI,GAAJ,IAAI,CAAQ;QAAkD,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IAAC,CAAC;CAChI"}
|