@portel/photon-core 1.5.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auto-ui.js +1 -1
- package/dist/auto-ui.js.map +1 -1
- package/dist/base.d.ts +1 -1
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +2 -2
- package/dist/base.js.map +1 -1
- package/dist/cli-ui-renderer.js +1 -1
- package/dist/cli-ui-renderer.js.map +1 -1
- package/dist/design-system/index.d.ts +21 -0
- package/dist/design-system/index.d.ts.map +1 -0
- package/dist/design-system/index.js +27 -0
- package/dist/design-system/index.js.map +1 -0
- package/dist/design-system/tokens.d.ts +149 -0
- package/dist/design-system/tokens.d.ts.map +1 -0
- package/dist/design-system/tokens.js +413 -0
- package/dist/design-system/tokens.js.map +1 -0
- package/dist/design-system/transaction-ui.d.ts +70 -0
- package/dist/design-system/transaction-ui.d.ts.map +1 -0
- package/dist/design-system/transaction-ui.js +982 -0
- package/dist/design-system/transaction-ui.js.map +1 -0
- package/dist/generator.d.ts +56 -6
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js.map +1 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -56
- package/dist/index.js.map +1 -1
- package/dist/io.d.ts +103 -2
- package/dist/io.d.ts.map +1 -1
- package/dist/io.js +37 -1
- package/dist/io.js.map +1 -1
- package/dist/rendering/components.d.ts +29 -0
- package/dist/rendering/components.d.ts.map +1 -0
- package/dist/rendering/components.js +773 -0
- package/dist/rendering/components.js.map +1 -0
- package/dist/rendering/field-analyzer.d.ts +48 -0
- package/dist/rendering/field-analyzer.d.ts.map +1 -0
- package/dist/rendering/field-analyzer.js +270 -0
- package/dist/rendering/field-analyzer.js.map +1 -0
- package/dist/rendering/field-renderers.d.ts +64 -0
- package/dist/rendering/field-renderers.d.ts.map +1 -0
- package/dist/rendering/field-renderers.js +317 -0
- package/dist/rendering/field-renderers.js.map +1 -0
- package/dist/rendering/index.d.ts +28 -0
- package/dist/rendering/index.d.ts.map +1 -0
- package/dist/rendering/index.js +60 -0
- package/dist/rendering/index.js.map +1 -0
- package/dist/rendering/layout-selector.d.ts +48 -0
- package/dist/rendering/layout-selector.d.ts.map +1 -0
- package/dist/rendering/layout-selector.js +347 -0
- package/dist/rendering/layout-selector.js.map +1 -0
- package/dist/rendering/template-engine.d.ts +41 -0
- package/dist/rendering/template-engine.d.ts.map +1 -0
- package/dist/rendering/template-engine.js +236 -0
- package/dist/rendering/template-engine.js.map +1 -0
- package/dist/schema-extractor.d.ts +30 -0
- package/dist/schema-extractor.d.ts.map +1 -1
- package/dist/schema-extractor.js +205 -12
- package/dist/schema-extractor.js.map +1 -1
- package/dist/stateful.js +1 -1
- package/dist/stateful.js.map +1 -1
- package/dist/types.d.ts +9 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ucp/ap2/handlers.d.ts +242 -0
- package/dist/ucp/ap2/handlers.d.ts.map +1 -0
- package/dist/ucp/ap2/handlers.js +482 -0
- package/dist/ucp/ap2/handlers.js.map +1 -0
- package/dist/ucp/ap2/mandates.d.ts +95 -0
- package/dist/ucp/ap2/mandates.d.ts.map +1 -0
- package/dist/ucp/ap2/mandates.js +234 -0
- package/dist/ucp/ap2/mandates.js.map +1 -0
- package/dist/ucp/ap2/types.d.ts +305 -0
- package/dist/ucp/ap2/types.d.ts.map +1 -0
- package/dist/ucp/ap2/types.js +8 -0
- package/dist/ucp/ap2/types.js.map +1 -0
- package/dist/ucp/capabilities/checkout.d.ts +118 -0
- package/dist/ucp/capabilities/checkout.d.ts.map +1 -0
- package/dist/ucp/capabilities/checkout.js +344 -0
- package/dist/ucp/capabilities/checkout.js.map +1 -0
- package/dist/ucp/capabilities/identity.d.ts +130 -0
- package/dist/ucp/capabilities/identity.d.ts.map +1 -0
- package/dist/ucp/capabilities/identity.js +290 -0
- package/dist/ucp/capabilities/identity.js.map +1 -0
- package/dist/ucp/capabilities/order.d.ts +142 -0
- package/dist/ucp/capabilities/order.d.ts.map +1 -0
- package/dist/ucp/capabilities/order.js +383 -0
- package/dist/ucp/capabilities/order.js.map +1 -0
- package/dist/ucp/index.d.ts +18 -0
- package/dist/ucp/index.d.ts.map +1 -0
- package/dist/ucp/index.js +19 -0
- package/dist/ucp/index.js.map +1 -0
- package/dist/ucp/manifest.d.ts +62 -0
- package/dist/ucp/manifest.d.ts.map +1 -0
- package/dist/ucp/manifest.js +180 -0
- package/dist/ucp/manifest.js.map +1 -0
- package/dist/ucp/types.d.ts +327 -0
- package/dist/ucp/types.d.ts.map +1 -0
- package/dist/ucp/types.js +8 -0
- package/dist/ucp/types.js.map +1 -0
- package/package.json +3 -4
- package/src/auto-ui.ts +1 -1
- package/src/base.ts +2 -2
- package/src/cli-ui-renderer.ts +1 -1
- package/src/design-system/index.ts +30 -0
- package/src/design-system/tokens.ts +451 -0
- package/src/design-system/transaction-ui.ts +1038 -0
- package/src/generator.ts +58 -2
- package/src/index.ts +135 -124
- package/src/io.ts +108 -3
- package/src/rendering/components.ts +785 -0
- package/src/rendering/field-analyzer.ts +299 -0
- package/src/rendering/field-renderers.ts +356 -0
- package/src/rendering/index.ts +63 -0
- package/src/rendering/layout-selector.ts +390 -0
- package/src/rendering/template-engine.ts +254 -0
- package/src/schema-extractor.ts +225 -12
- package/src/stateful.ts +1 -1
- package/src/types.ts +10 -1
- package/src/ucp/ap2/handlers.ts +779 -0
- package/src/ucp/ap2/mandates.ts +354 -0
- package/src/ucp/ap2/types.ts +441 -0
- package/src/ucp/capabilities/checkout.ts +497 -0
- package/src/ucp/capabilities/identity.ts +425 -0
- package/src/ucp/capabilities/order.ts +549 -0
- package/src/ucp/index.ts +27 -0
- package/src/ucp/manifest.ts +257 -0
- package/src/ucp/types.ts +454 -0
- package/dist/cli-formatter.d.ts +0 -92
- package/dist/cli-formatter.d.ts.map +0 -1
- package/dist/cli-formatter.js +0 -486
- package/dist/cli-formatter.js.map +0 -1
- package/dist/context.d.ts +0 -6
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -3
- package/dist/context.js.map +0 -1
- package/dist/elicit.d.ts +0 -93
- package/dist/elicit.d.ts.map +0 -1
- package/dist/elicit.js +0 -373
- package/dist/elicit.js.map +0 -1
- package/dist/mcp-client.d.ts +0 -218
- package/dist/mcp-client.d.ts.map +0 -1
- package/dist/mcp-client.js +0 -424
- package/dist/mcp-client.js.map +0 -1
- package/dist/mcp-sdk-transport.d.ts +0 -88
- package/dist/mcp-sdk-transport.d.ts.map +0 -1
- package/dist/mcp-sdk-transport.js +0 -360
- package/dist/mcp-sdk-transport.js.map +0 -1
- package/dist/photon-config.d.ts +0 -86
- package/dist/photon-config.d.ts.map +0 -1
- package/dist/photon-config.js +0 -156
- package/dist/photon-config.js.map +0 -1
- package/dist/progress.d.ts +0 -93
- package/dist/progress.d.ts.map +0 -1
- package/dist/progress.js +0 -195
- package/dist/progress.js.map +0 -1
- package/src/cli-formatter.ts +0 -579
- package/src/context.ts +0 -7
- package/src/elicit.ts +0 -438
- package/src/mcp-client.ts +0 -561
- package/src/mcp-sdk-transport.ts +0 -449
- package/src/photon-config.ts +0 -201
- package/src/progress.ts +0 -224
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UCP Identity Linking Capability Implementation
|
|
3
|
+
*
|
|
4
|
+
* Provides OAuth 2.0 based identity linking for agent authorization.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as crypto from 'crypto';
|
|
8
|
+
import {
|
|
9
|
+
OAuthConfig,
|
|
10
|
+
TokenResponse,
|
|
11
|
+
IdentityLink
|
|
12
|
+
} from '../types.js';
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Token Storage Interface
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
export interface TokenStorage {
|
|
19
|
+
storeAuthCode(code: string, data: AuthCodeData): Promise<void>;
|
|
20
|
+
getAuthCode(code: string): Promise<AuthCodeData | null>;
|
|
21
|
+
deleteAuthCode(code: string): Promise<void>;
|
|
22
|
+
|
|
23
|
+
storeToken(token: string, data: TokenData): Promise<void>;
|
|
24
|
+
getToken(token: string): Promise<TokenData | null>;
|
|
25
|
+
deleteToken(token: string): Promise<void>;
|
|
26
|
+
|
|
27
|
+
storeRefreshToken(token: string, data: RefreshTokenData): Promise<void>;
|
|
28
|
+
getRefreshToken(token: string): Promise<RefreshTokenData | null>;
|
|
29
|
+
deleteRefreshToken(token: string): Promise<void>;
|
|
30
|
+
|
|
31
|
+
storeIdentityLink(link: IdentityLink): Promise<void>;
|
|
32
|
+
getIdentityLink(agentId: string): Promise<IdentityLink | null>;
|
|
33
|
+
deleteIdentityLink(agentId: string): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface AuthCodeData {
|
|
37
|
+
agentId: string;
|
|
38
|
+
scopes: string[];
|
|
39
|
+
redirectUri: string;
|
|
40
|
+
expiresAt: string;
|
|
41
|
+
state?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface TokenData {
|
|
45
|
+
agentId: string;
|
|
46
|
+
scopes: string[];
|
|
47
|
+
expiresAt: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface RefreshTokenData {
|
|
51
|
+
agentId: string;
|
|
52
|
+
scopes: string[];
|
|
53
|
+
accessToken: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* In-memory token storage (for development/testing)
|
|
58
|
+
*/
|
|
59
|
+
export class MemoryTokenStorage implements TokenStorage {
|
|
60
|
+
private authCodes = new Map<string, AuthCodeData>();
|
|
61
|
+
private tokens = new Map<string, TokenData>();
|
|
62
|
+
private refreshTokens = new Map<string, RefreshTokenData>();
|
|
63
|
+
private identityLinks = new Map<string, IdentityLink>();
|
|
64
|
+
|
|
65
|
+
async storeAuthCode(code: string, data: AuthCodeData): Promise<void> {
|
|
66
|
+
this.authCodes.set(code, data);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async getAuthCode(code: string): Promise<AuthCodeData | null> {
|
|
70
|
+
const data = this.authCodes.get(code);
|
|
71
|
+
if (!data) return null;
|
|
72
|
+
|
|
73
|
+
if (new Date(data.expiresAt) < new Date()) {
|
|
74
|
+
this.authCodes.delete(code);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return data;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async deleteAuthCode(code: string): Promise<void> {
|
|
82
|
+
this.authCodes.delete(code);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async storeToken(token: string, data: TokenData): Promise<void> {
|
|
86
|
+
this.tokens.set(token, data);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async getToken(token: string): Promise<TokenData | null> {
|
|
90
|
+
const data = this.tokens.get(token);
|
|
91
|
+
if (!data) return null;
|
|
92
|
+
|
|
93
|
+
if (new Date(data.expiresAt) < new Date()) {
|
|
94
|
+
this.tokens.delete(token);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return data;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async deleteToken(token: string): Promise<void> {
|
|
102
|
+
this.tokens.delete(token);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async storeRefreshToken(token: string, data: RefreshTokenData): Promise<void> {
|
|
106
|
+
this.refreshTokens.set(token, data);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async getRefreshToken(token: string): Promise<RefreshTokenData | null> {
|
|
110
|
+
return this.refreshTokens.get(token) || null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async deleteRefreshToken(token: string): Promise<void> {
|
|
114
|
+
this.refreshTokens.delete(token);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async storeIdentityLink(link: IdentityLink): Promise<void> {
|
|
118
|
+
this.identityLinks.set(link.agentId, link);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async getIdentityLink(agentId: string): Promise<IdentityLink | null> {
|
|
122
|
+
return this.identityLinks.get(agentId) || null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async deleteIdentityLink(agentId: string): Promise<void> {
|
|
126
|
+
this.identityLinks.delete(agentId);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// Identity Service
|
|
132
|
+
// ============================================================================
|
|
133
|
+
|
|
134
|
+
export interface IdentityServiceConfig {
|
|
135
|
+
baseUrl: string;
|
|
136
|
+
allowedScopes: string[];
|
|
137
|
+
authCodeTTLSeconds: number;
|
|
138
|
+
accessTokenTTLSeconds: number;
|
|
139
|
+
refreshTokenTTLDays: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const DEFAULT_CONFIG: IdentityServiceConfig = {
|
|
143
|
+
baseUrl: '',
|
|
144
|
+
allowedScopes: ['checkout.read', 'checkout.write', 'order.read', 'order.write'],
|
|
145
|
+
authCodeTTLSeconds: 600, // 10 minutes
|
|
146
|
+
accessTokenTTLSeconds: 3600, // 1 hour
|
|
147
|
+
refreshTokenTTLDays: 30
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export class IdentityService {
|
|
151
|
+
private storage: TokenStorage;
|
|
152
|
+
private config: IdentityServiceConfig;
|
|
153
|
+
|
|
154
|
+
constructor(config?: Partial<IdentityServiceConfig>, storage?: TokenStorage) {
|
|
155
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
156
|
+
this.storage = storage || new MemoryTokenStorage();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// --------------------------------------------------------------------------
|
|
160
|
+
// OAuth Configuration
|
|
161
|
+
// --------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get OAuth configuration for discovery
|
|
165
|
+
*/
|
|
166
|
+
async getOAuthConfig(): Promise<OAuthConfig> {
|
|
167
|
+
return {
|
|
168
|
+
authorizationEndpoint: `${this.config.baseUrl}/oauth/authorize`,
|
|
169
|
+
tokenEndpoint: `${this.config.baseUrl}/oauth/token`,
|
|
170
|
+
revocationEndpoint: `${this.config.baseUrl}/oauth/revoke`,
|
|
171
|
+
scopes: this.config.allowedScopes
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// --------------------------------------------------------------------------
|
|
176
|
+
// Authorization Flow
|
|
177
|
+
// --------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Generate authorization URL for agent
|
|
181
|
+
*/
|
|
182
|
+
async requestAuthorization(params: {
|
|
183
|
+
agentId: string;
|
|
184
|
+
scopes: string[];
|
|
185
|
+
redirectUri: string;
|
|
186
|
+
state?: string;
|
|
187
|
+
}): Promise<{ authorizationUrl: string }> {
|
|
188
|
+
// Validate scopes
|
|
189
|
+
const invalidScopes = params.scopes.filter(s => !this.config.allowedScopes.includes(s));
|
|
190
|
+
if (invalidScopes.length > 0) {
|
|
191
|
+
throw new Error(`Invalid scopes: ${invalidScopes.join(', ')}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Generate auth code
|
|
195
|
+
const code = this.generateCode();
|
|
196
|
+
const expiresAt = new Date(Date.now() + this.config.authCodeTTLSeconds * 1000);
|
|
197
|
+
|
|
198
|
+
await this.storage.storeAuthCode(code, {
|
|
199
|
+
agentId: params.agentId,
|
|
200
|
+
scopes: params.scopes,
|
|
201
|
+
redirectUri: params.redirectUri,
|
|
202
|
+
expiresAt: expiresAt.toISOString(),
|
|
203
|
+
state: params.state
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Build authorization URL
|
|
207
|
+
const url = new URL(`${this.config.baseUrl}/oauth/authorize`);
|
|
208
|
+
url.searchParams.set('code', code);
|
|
209
|
+
url.searchParams.set('agent_id', params.agentId);
|
|
210
|
+
url.searchParams.set('scope', params.scopes.join(' '));
|
|
211
|
+
url.searchParams.set('redirect_uri', params.redirectUri);
|
|
212
|
+
if (params.state) {
|
|
213
|
+
url.searchParams.set('state', params.state);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return { authorizationUrl: url.toString() };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Handle user approval (called after user consents)
|
|
221
|
+
*/
|
|
222
|
+
async approveAuthorization(code: string, userId: string): Promise<{
|
|
223
|
+
redirectUrl: string;
|
|
224
|
+
}> {
|
|
225
|
+
const authData = await this.storage.getAuthCode(code);
|
|
226
|
+
if (!authData) {
|
|
227
|
+
throw new Error('Invalid or expired authorization code');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Create identity link
|
|
231
|
+
const link: IdentityLink = {
|
|
232
|
+
id: `link_${crypto.randomUUID()}`,
|
|
233
|
+
agentId: authData.agentId,
|
|
234
|
+
userId,
|
|
235
|
+
scopes: authData.scopes,
|
|
236
|
+
createdAt: new Date().toISOString()
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
await this.storage.storeIdentityLink(link);
|
|
240
|
+
|
|
241
|
+
// Build redirect URL with code
|
|
242
|
+
const redirectUrl = new URL(authData.redirectUri);
|
|
243
|
+
redirectUrl.searchParams.set('code', code);
|
|
244
|
+
if (authData.state) {
|
|
245
|
+
redirectUrl.searchParams.set('state', authData.state);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return { redirectUrl: redirectUrl.toString() };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// --------------------------------------------------------------------------
|
|
252
|
+
// Token Exchange
|
|
253
|
+
// --------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Exchange authorization code for tokens
|
|
257
|
+
*/
|
|
258
|
+
async exchangeCode(params: {
|
|
259
|
+
code: string;
|
|
260
|
+
redirectUri: string;
|
|
261
|
+
}): Promise<TokenResponse> {
|
|
262
|
+
const authData = await this.storage.getAuthCode(params.code);
|
|
263
|
+
|
|
264
|
+
if (!authData) {
|
|
265
|
+
throw new Error('Invalid or expired authorization code');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (authData.redirectUri !== params.redirectUri) {
|
|
269
|
+
throw new Error('Redirect URI mismatch');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Delete used auth code
|
|
273
|
+
await this.storage.deleteAuthCode(params.code);
|
|
274
|
+
|
|
275
|
+
// Generate tokens
|
|
276
|
+
const accessToken = this.generateToken();
|
|
277
|
+
const refreshToken = this.generateToken();
|
|
278
|
+
|
|
279
|
+
const accessTokenExpires = new Date(Date.now() + this.config.accessTokenTTLSeconds * 1000);
|
|
280
|
+
|
|
281
|
+
// Store tokens
|
|
282
|
+
await this.storage.storeToken(accessToken, {
|
|
283
|
+
agentId: authData.agentId,
|
|
284
|
+
scopes: authData.scopes,
|
|
285
|
+
expiresAt: accessTokenExpires.toISOString()
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
await this.storage.storeRefreshToken(refreshToken, {
|
|
289
|
+
agentId: authData.agentId,
|
|
290
|
+
scopes: authData.scopes,
|
|
291
|
+
accessToken
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
accessToken,
|
|
296
|
+
tokenType: 'Bearer',
|
|
297
|
+
expiresIn: this.config.accessTokenTTLSeconds,
|
|
298
|
+
refreshToken,
|
|
299
|
+
scope: authData.scopes.join(' ')
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Refresh access token
|
|
305
|
+
*/
|
|
306
|
+
async refreshToken(refreshToken: string): Promise<TokenResponse> {
|
|
307
|
+
const refreshData = await this.storage.getRefreshToken(refreshToken);
|
|
308
|
+
|
|
309
|
+
if (!refreshData) {
|
|
310
|
+
throw new Error('Invalid refresh token');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Delete old access token
|
|
314
|
+
await this.storage.deleteToken(refreshData.accessToken);
|
|
315
|
+
|
|
316
|
+
// Generate new access token
|
|
317
|
+
const newAccessToken = this.generateToken();
|
|
318
|
+
const accessTokenExpires = new Date(Date.now() + this.config.accessTokenTTLSeconds * 1000);
|
|
319
|
+
|
|
320
|
+
await this.storage.storeToken(newAccessToken, {
|
|
321
|
+
agentId: refreshData.agentId,
|
|
322
|
+
scopes: refreshData.scopes,
|
|
323
|
+
expiresAt: accessTokenExpires.toISOString()
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Update refresh token reference
|
|
327
|
+
await this.storage.storeRefreshToken(refreshToken, {
|
|
328
|
+
...refreshData,
|
|
329
|
+
accessToken: newAccessToken
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
accessToken: newAccessToken,
|
|
334
|
+
tokenType: 'Bearer',
|
|
335
|
+
expiresIn: this.config.accessTokenTTLSeconds,
|
|
336
|
+
scope: refreshData.scopes.join(' ')
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// --------------------------------------------------------------------------
|
|
341
|
+
// Token Validation
|
|
342
|
+
// --------------------------------------------------------------------------
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Validate access token
|
|
346
|
+
*/
|
|
347
|
+
async validateToken(token: string): Promise<{
|
|
348
|
+
valid: boolean;
|
|
349
|
+
agentId?: string;
|
|
350
|
+
scopes?: string[];
|
|
351
|
+
}> {
|
|
352
|
+
const tokenData = await this.storage.getToken(token);
|
|
353
|
+
|
|
354
|
+
if (!tokenData) {
|
|
355
|
+
return { valid: false };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
valid: true,
|
|
360
|
+
agentId: tokenData.agentId,
|
|
361
|
+
scopes: tokenData.scopes
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Check if token has required scope
|
|
367
|
+
*/
|
|
368
|
+
async hasScope(token: string, requiredScope: string): Promise<boolean> {
|
|
369
|
+
const validation = await this.validateToken(token);
|
|
370
|
+
return validation.valid && (validation.scopes?.includes(requiredScope) || false);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// --------------------------------------------------------------------------
|
|
374
|
+
// Revocation
|
|
375
|
+
// --------------------------------------------------------------------------
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Revoke token
|
|
379
|
+
*/
|
|
380
|
+
async revokeToken(token: string): Promise<void> {
|
|
381
|
+
// Try to find and delete as access token
|
|
382
|
+
const tokenData = await this.storage.getToken(token);
|
|
383
|
+
if (tokenData) {
|
|
384
|
+
await this.storage.deleteToken(token);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Try to find and delete as refresh token
|
|
389
|
+
const refreshData = await this.storage.getRefreshToken(token);
|
|
390
|
+
if (refreshData) {
|
|
391
|
+
await this.storage.deleteToken(refreshData.accessToken);
|
|
392
|
+
await this.storage.deleteRefreshToken(token);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Revoke all tokens for an agent
|
|
398
|
+
*/
|
|
399
|
+
async revokeAgent(agentId: string): Promise<void> {
|
|
400
|
+
await this.storage.deleteIdentityLink(agentId);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// --------------------------------------------------------------------------
|
|
404
|
+
// Identity Links
|
|
405
|
+
// --------------------------------------------------------------------------
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Get identity link for agent
|
|
409
|
+
*/
|
|
410
|
+
async getIdentityLink(agentId: string): Promise<IdentityLink | null> {
|
|
411
|
+
return this.storage.getIdentityLink(agentId);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// --------------------------------------------------------------------------
|
|
415
|
+
// Helpers
|
|
416
|
+
// --------------------------------------------------------------------------
|
|
417
|
+
|
|
418
|
+
private generateCode(): string {
|
|
419
|
+
return crypto.randomBytes(32).toString('base64url');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private generateToken(): string {
|
|
423
|
+
return crypto.randomBytes(48).toString('base64url');
|
|
424
|
+
}
|
|
425
|
+
}
|