@kya-os/mcp-i-cloudflare 1.5.8-canary.4 → 1.5.8-canary.40
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 +130 -0
- package/dist/__tests__/e2e/test-config.d.ts +37 -0
- package/dist/__tests__/e2e/test-config.d.ts.map +1 -0
- package/dist/__tests__/e2e/test-config.js +62 -0
- package/dist/__tests__/e2e/test-config.js.map +1 -0
- package/dist/adapter.d.ts +44 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +655 -87
- package/dist/adapter.js.map +1 -1
- package/dist/agent.d.ts +8 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +114 -5
- package/dist/agent.js.map +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +19 -3
- package/dist/app.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +33 -4
- package/dist/config.js.map +1 -1
- package/dist/helpers/env-mapper.d.ts +60 -1
- package/dist/helpers/env-mapper.d.ts.map +1 -1
- package/dist/helpers/env-mapper.js +136 -6
- package/dist/helpers/env-mapper.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/runtime/audit-logger.d.ts +96 -0
- package/dist/runtime/audit-logger.d.ts.map +1 -0
- package/dist/runtime/audit-logger.js +276 -0
- package/dist/runtime/audit-logger.js.map +1 -0
- package/dist/runtime/oauth-handler.d.ts +5 -0
- package/dist/runtime/oauth-handler.d.ts.map +1 -1
- package/dist/runtime/oauth-handler.js +152 -35
- package/dist/runtime/oauth-handler.js.map +1 -1
- package/dist/runtime.d.ts +12 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +34 -4
- package/dist/runtime.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +7 -1
- package/dist/server.js.map +1 -1
- package/dist/services/admin.service.d.ts.map +1 -1
- package/dist/services/admin.service.js +15 -1
- package/dist/services/admin.service.js.map +1 -1
- package/dist/services/consent-audit.service.d.ts +91 -0
- package/dist/services/consent-audit.service.d.ts.map +1 -0
- package/dist/services/consent-audit.service.js +243 -0
- package/dist/services/consent-audit.service.js.map +1 -0
- package/dist/services/consent-config.service.d.ts +2 -2
- package/dist/services/consent-config.service.d.ts.map +1 -1
- package/dist/services/consent-config.service.js +55 -24
- package/dist/services/consent-config.service.js.map +1 -1
- package/dist/services/consent.service.d.ts +49 -1
- package/dist/services/consent.service.d.ts.map +1 -1
- package/dist/services/consent.service.js +1491 -28
- package/dist/services/consent.service.js.map +1 -1
- package/dist/services/delegation.service.d.ts.map +1 -1
- package/dist/services/delegation.service.js +67 -29
- package/dist/services/delegation.service.js.map +1 -1
- package/dist/services/proof.service.d.ts +5 -3
- package/dist/services/proof.service.d.ts.map +1 -1
- package/dist/services/proof.service.js +35 -8
- package/dist/services/proof.service.js.map +1 -1
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +13 -9
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Audit Logger
|
|
3
|
+
*
|
|
4
|
+
* Cloudflare Workers-compatible implementation of IAuditLogger using Web Crypto API.
|
|
5
|
+
* This implementation uses Web Crypto API instead of Node.js crypto for compatibility
|
|
6
|
+
* with Cloudflare Workers environment.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Format milliseconds into human-readable interval string
|
|
10
|
+
*/
|
|
11
|
+
function formatTimeInterval(ms) {
|
|
12
|
+
if (ms === undefined || ms === null)
|
|
13
|
+
return "unknown";
|
|
14
|
+
if (ms === 0)
|
|
15
|
+
return "0ms";
|
|
16
|
+
const TIME_INTERVALS = {
|
|
17
|
+
SECOND: 1000,
|
|
18
|
+
MINUTE: 60 * 1000,
|
|
19
|
+
HOUR: 60 * 60 * 1000,
|
|
20
|
+
DAY: 24 * 60 * 60 * 1000,
|
|
21
|
+
WEEK: 7 * 24 * 60 * 60 * 1000,
|
|
22
|
+
};
|
|
23
|
+
if (ms % TIME_INTERVALS.WEEK === 0) {
|
|
24
|
+
const weeks = ms / TIME_INTERVALS.WEEK;
|
|
25
|
+
return weeks === 1 ? "weekly" : `${weeks}-weekly`;
|
|
26
|
+
}
|
|
27
|
+
if (ms % TIME_INTERVALS.DAY === 0) {
|
|
28
|
+
const days = ms / TIME_INTERVALS.DAY;
|
|
29
|
+
return days === 1 ? "daily" : `${days}-daily`;
|
|
30
|
+
}
|
|
31
|
+
if (ms % TIME_INTERVALS.HOUR === 0) {
|
|
32
|
+
const hours = ms / TIME_INTERVALS.HOUR;
|
|
33
|
+
return hours === 1 ? "hourly" : `${hours}-hourly`;
|
|
34
|
+
}
|
|
35
|
+
if (ms % TIME_INTERVALS.MINUTE === 0) {
|
|
36
|
+
const minutes = ms / TIME_INTERVALS.MINUTE;
|
|
37
|
+
return minutes === 1 ? "minutely" : `${minutes}-minutely`;
|
|
38
|
+
}
|
|
39
|
+
if (ms % TIME_INTERVALS.SECOND === 0) {
|
|
40
|
+
const seconds = ms / TIME_INTERVALS.SECOND;
|
|
41
|
+
return seconds === 1 ? "every-second" : `${seconds}-secondly`;
|
|
42
|
+
}
|
|
43
|
+
return `${ms}ms`;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Cloudflare-compatible audit logger implementation
|
|
47
|
+
*
|
|
48
|
+
* Uses Web Crypto API for cryptographic operations instead of Node.js crypto.
|
|
49
|
+
* Implements the same audit.v1 format and rotation logic as the Node.js version.
|
|
50
|
+
*/
|
|
51
|
+
export class CloudflareAuditLogger {
|
|
52
|
+
config;
|
|
53
|
+
sessionAuditLog = new Set(); // Track first call per session
|
|
54
|
+
totalRecordsLogged = 0; // Total records logged (for count rotation)
|
|
55
|
+
currentLogSize = 0; // Current log size in bytes (for size rotation)
|
|
56
|
+
lastRotationTime = Date.now(); // Last rotation timestamp (for time rotation)
|
|
57
|
+
destroyed = false; // Track if logger has been destroyed
|
|
58
|
+
constructor(config = {}) {
|
|
59
|
+
const rotationConfig = config.rotation
|
|
60
|
+
? {
|
|
61
|
+
strategy: "custom",
|
|
62
|
+
...config.rotation,
|
|
63
|
+
}
|
|
64
|
+
: undefined;
|
|
65
|
+
this.config = {
|
|
66
|
+
enabled: true,
|
|
67
|
+
logFunction: console.log,
|
|
68
|
+
includePayloads: false,
|
|
69
|
+
...config,
|
|
70
|
+
rotation: rotationConfig,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Log an audit record (with session deduplication)
|
|
75
|
+
*/
|
|
76
|
+
async logAuditRecord(context) {
|
|
77
|
+
if (this.destroyed) {
|
|
78
|
+
throw new Error("CloudflareAuditLogger has been destroyed");
|
|
79
|
+
}
|
|
80
|
+
if (!this.config.enabled) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Check if this is the first call for this session
|
|
84
|
+
const sessionKey = `${context.session.sessionId}:${context.session.audience}`;
|
|
85
|
+
if (this.sessionAuditLog.has(sessionKey)) {
|
|
86
|
+
return; // Already logged for this session
|
|
87
|
+
}
|
|
88
|
+
// Mark session as logged
|
|
89
|
+
this.sessionAuditLog.add(sessionKey);
|
|
90
|
+
// Create audit record
|
|
91
|
+
// Extract kid from identity (may be kid, keyId, or derived from did)
|
|
92
|
+
const kid = context.identity.kid ||
|
|
93
|
+
context.identity.keyId ||
|
|
94
|
+
context.identity.did.split(":").pop() ||
|
|
95
|
+
"unknown";
|
|
96
|
+
const auditRecord = {
|
|
97
|
+
version: "audit.v1",
|
|
98
|
+
ts: Math.floor(Date.now() / 1000),
|
|
99
|
+
session: context.session.sessionId,
|
|
100
|
+
audience: context.session.audience,
|
|
101
|
+
did: context.identity.did,
|
|
102
|
+
kid,
|
|
103
|
+
reqHash: context.requestHash,
|
|
104
|
+
resHash: context.responseHash,
|
|
105
|
+
verified: context.verified,
|
|
106
|
+
scope: context.scopeId || "-",
|
|
107
|
+
};
|
|
108
|
+
// Format as frozen audit line
|
|
109
|
+
const auditLine = this.formatAuditLine(auditRecord);
|
|
110
|
+
// Track size in bytes (UTF-8) - using TextEncoder instead of Buffer
|
|
111
|
+
const encoder = new TextEncoder();
|
|
112
|
+
const sizeBytes = encoder.encode(auditLine).length;
|
|
113
|
+
this.currentLogSize += sizeBytes;
|
|
114
|
+
this.totalRecordsLogged++;
|
|
115
|
+
// Emit audit record
|
|
116
|
+
this.config.logFunction(auditLine);
|
|
117
|
+
// Check if rotation is needed (event-driven)
|
|
118
|
+
await this.checkRotation();
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Log an event (without session deduplication)
|
|
122
|
+
*/
|
|
123
|
+
async logEvent(context) {
|
|
124
|
+
if (this.destroyed) {
|
|
125
|
+
throw new Error("CloudflareAuditLogger has been destroyed");
|
|
126
|
+
}
|
|
127
|
+
if (!this.config.enabled) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Generate event hash using Web Crypto API
|
|
131
|
+
const eventHash = await this.hashEvent(context.eventType, context.eventData);
|
|
132
|
+
// Create audit record (same format as regular audit logs)
|
|
133
|
+
// Extract kid from identity (may be kid, keyId, or derived from did)
|
|
134
|
+
const kid = context.identity.kid ||
|
|
135
|
+
context.identity.keyId ||
|
|
136
|
+
context.identity.did.split(":").pop() ||
|
|
137
|
+
"unknown";
|
|
138
|
+
const auditRecord = {
|
|
139
|
+
version: "audit.v1",
|
|
140
|
+
ts: Math.floor(Date.now() / 1000),
|
|
141
|
+
session: context.session.sessionId,
|
|
142
|
+
audience: context.session.audience,
|
|
143
|
+
did: context.identity.did,
|
|
144
|
+
kid,
|
|
145
|
+
reqHash: `sha256:${eventHash}`,
|
|
146
|
+
resHash: `sha256:${eventHash}`, // Same hash for events
|
|
147
|
+
verified: "yes",
|
|
148
|
+
scope: context.eventType, // Use eventType as scope
|
|
149
|
+
};
|
|
150
|
+
// Format and log (NO session deduplication check)
|
|
151
|
+
const auditLine = this.formatAuditLine(auditRecord);
|
|
152
|
+
// Track size and count
|
|
153
|
+
const encoder = new TextEncoder();
|
|
154
|
+
const sizeBytes = encoder.encode(auditLine).length;
|
|
155
|
+
this.currentLogSize += sizeBytes;
|
|
156
|
+
this.totalRecordsLogged++;
|
|
157
|
+
// Emit audit record
|
|
158
|
+
this.config.logFunction(auditLine);
|
|
159
|
+
// Check rotation
|
|
160
|
+
await this.checkRotation();
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Generate deterministic hash for event using Web Crypto API
|
|
164
|
+
*/
|
|
165
|
+
async hashEvent(type, data) {
|
|
166
|
+
const content = JSON.stringify({
|
|
167
|
+
type,
|
|
168
|
+
data,
|
|
169
|
+
ts: Date.now(),
|
|
170
|
+
nonce: this.generateRandomHex(16),
|
|
171
|
+
});
|
|
172
|
+
// Use Web Crypto API for SHA-256 hashing
|
|
173
|
+
const encoder = new TextEncoder();
|
|
174
|
+
const dataBuffer = encoder.encode(content);
|
|
175
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", dataBuffer);
|
|
176
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
177
|
+
const hashHex = hashArray
|
|
178
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
179
|
+
.join("");
|
|
180
|
+
return hashHex;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Generate random hex string using Web Crypto API
|
|
184
|
+
*/
|
|
185
|
+
generateRandomHex(length) {
|
|
186
|
+
const randomBytes = crypto.getRandomValues(new Uint8Array(length));
|
|
187
|
+
return Array.from(randomBytes)
|
|
188
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
189
|
+
.join("");
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Format audit record as frozen audit line
|
|
193
|
+
* Format: audit.v1 ts=<unix> session=<id> audience=<host> did=<did> kid=<kid> reqHash=<sha256:..> resHash=<sha256:..> verified=yes|no scope=<scopeId|->
|
|
194
|
+
*/
|
|
195
|
+
formatAuditLine(record) {
|
|
196
|
+
const fields = [
|
|
197
|
+
`${record.version}`,
|
|
198
|
+
`ts=${record.ts}`,
|
|
199
|
+
`session=${record.session}`,
|
|
200
|
+
`audience=${record.audience}`,
|
|
201
|
+
`did=${record.did}`,
|
|
202
|
+
`kid=${record.kid}`,
|
|
203
|
+
`reqHash=${record.reqHash}`,
|
|
204
|
+
`resHash=${record.resHash}`,
|
|
205
|
+
`verified=${record.verified}`,
|
|
206
|
+
`scope=${record.scope}`,
|
|
207
|
+
];
|
|
208
|
+
return fields.join(" ");
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Check if rotation is needed and trigger if necessary (event-driven)
|
|
212
|
+
*/
|
|
213
|
+
async checkRotation() {
|
|
214
|
+
if (!this.config.rotation) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const { strategy, sizeLimit, timeInterval, countThreshold, hooks } = this.config.rotation;
|
|
218
|
+
let shouldRotate = false;
|
|
219
|
+
let trigger = "";
|
|
220
|
+
// Size-based rotation
|
|
221
|
+
if (strategy === "size" && sizeLimit && this.currentLogSize >= sizeLimit) {
|
|
222
|
+
shouldRotate = true;
|
|
223
|
+
trigger = "size-limit";
|
|
224
|
+
await hooks?.onSizeLimit?.(this.currentLogSize, sizeLimit);
|
|
225
|
+
}
|
|
226
|
+
// Time-based rotation (event-driven, not timer-based)
|
|
227
|
+
if (strategy === "time" &&
|
|
228
|
+
timeInterval &&
|
|
229
|
+
Date.now() - this.lastRotationTime >= timeInterval) {
|
|
230
|
+
shouldRotate = true;
|
|
231
|
+
trigger = "time-interval";
|
|
232
|
+
const interval = formatTimeInterval(timeInterval);
|
|
233
|
+
await hooks?.onTimeBased?.(interval);
|
|
234
|
+
}
|
|
235
|
+
// Count-based rotation
|
|
236
|
+
if (strategy === "count" &&
|
|
237
|
+
countThreshold &&
|
|
238
|
+
this.totalRecordsLogged >= countThreshold) {
|
|
239
|
+
shouldRotate = true;
|
|
240
|
+
trigger = "count-threshold";
|
|
241
|
+
await hooks?.onCountThreshold?.(this.totalRecordsLogged, countThreshold);
|
|
242
|
+
}
|
|
243
|
+
// Trigger rotation if needed
|
|
244
|
+
if (shouldRotate) {
|
|
245
|
+
await this.rotateNow(trigger);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Rotate audit log now (manually triggered)
|
|
250
|
+
*/
|
|
251
|
+
async rotateNow(trigger = "manual") {
|
|
252
|
+
if (!this.config.rotation?.hooks?.onRotation) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const context = {
|
|
256
|
+
strategy: this.config.rotation.strategy || "custom",
|
|
257
|
+
trigger,
|
|
258
|
+
recordsLogged: this.totalRecordsLogged,
|
|
259
|
+
timestamp: Date.now(),
|
|
260
|
+
};
|
|
261
|
+
// Call rotation hook
|
|
262
|
+
await this.config.rotation.hooks.onRotation(context);
|
|
263
|
+
// Reset rotation counters
|
|
264
|
+
this.currentLogSize = 0;
|
|
265
|
+
this.lastRotationTime = Date.now();
|
|
266
|
+
this.totalRecordsLogged = 0;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Destroy the logger (cleanup)
|
|
270
|
+
*/
|
|
271
|
+
destroy() {
|
|
272
|
+
this.destroyed = true;
|
|
273
|
+
this.sessionAuditLog.clear();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=audit-logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-logger.js","sourceRoot":"","sources":["../../src/runtime/audit-logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@ export interface HonoContext {
|
|
|
14
14
|
header: (name: string, value: string) => void;
|
|
15
15
|
}
|
|
16
16
|
import type { ConsentService } from '../services/consent.service';
|
|
17
|
+
import type { OAuthSecurityService } from '../services/oauth-security.service';
|
|
17
18
|
export interface OAuthCallbackConfig {
|
|
18
19
|
/**
|
|
19
20
|
* AgentShield API URL (defaults to AGENTSHIELD_API_URL env var)
|
|
@@ -27,6 +28,10 @@ export interface OAuthCallbackConfig {
|
|
|
27
28
|
* ConsentService instance for OAuth identity linking (Phase 4)
|
|
28
29
|
*/
|
|
29
30
|
consentService?: ConsentService;
|
|
31
|
+
/**
|
|
32
|
+
* OAuthSecurityService instance for CSRF-protected state validation
|
|
33
|
+
*/
|
|
34
|
+
oauthSecurityService?: OAuthSecurityService;
|
|
30
35
|
/**
|
|
31
36
|
* Custom success HTML template (optional)
|
|
32
37
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-handler.d.ts","sourceRoot":"","sources":["../../src/runtime/oauth-handler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,GAAG,CAAC;IACT,GAAG,EAAE;QACH,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;QAC3C,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;KAC9C,CAAC;IACF,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAC;IAClD,IAAI,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAC;IACjD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAID,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"oauth-handler.d.ts","sourceRoot":"","sources":["../../src/runtime/oauth-handler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,GAAG,CAAC;IACT,GAAG,EAAE;QACH,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;QAC3C,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;KAC9C,CAAC;IACF,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAC;IAClD,IAAI,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAC;IACjD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAID,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAE/E,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;OAEG;IACH,iBAAiB,CAAC,EAAE,WAAW,CAAC;IAEhC;;OAEG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC;;OAEG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAE5C;;OAEG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,MAAM,CAAC;IAErD;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,MAAM,CAAC;IAElD;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA8KD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,GAAE,mBAAwB,IAC3D,GAAG,WAAW,uBAoY7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAoBpE"}
|
|
@@ -184,7 +184,7 @@ export function createOAuthCallbackHandler(config = {}) {
|
|
|
184
184
|
return async (c) => {
|
|
185
185
|
const env = c.env;
|
|
186
186
|
// Get configuration with defaults
|
|
187
|
-
const { agentShieldApiUrl = env.AGENTSHIELD_API_URL || 'https://hobbs.work', delegationStorage, consentService, successTemplate = defaultSuccessTemplate, errorTemplate = defaultErrorTemplate, autoClose = true, autoCloseDelay = 5000 } = config;
|
|
187
|
+
const { agentShieldApiUrl = env.AGENTSHIELD_API_URL || 'https://hobbs.work', delegationStorage, consentService, oauthSecurityService, successTemplate = defaultSuccessTemplate, errorTemplate = defaultErrorTemplate, autoClose = true, autoCloseDelay = 5000 } = config;
|
|
188
188
|
// Get query parameters
|
|
189
189
|
const code = c.req.query('code');
|
|
190
190
|
const stateParam = c.req.query('state');
|
|
@@ -192,7 +192,12 @@ export function createOAuthCallbackHandler(config = {}) {
|
|
|
192
192
|
// Handle OAuth errors
|
|
193
193
|
if (error) {
|
|
194
194
|
const errorDescription = c.req.query('error_description') || 'Authorization failed';
|
|
195
|
-
console.error('[OAuth] Error from provider:',
|
|
195
|
+
console.error('[OAuth] 🔒 SECURITY EVENT: Error from provider:', {
|
|
196
|
+
error,
|
|
197
|
+
errorDescription,
|
|
198
|
+
timestamp: new Date().toISOString(),
|
|
199
|
+
eventType: 'oauth_provider_error'
|
|
200
|
+
});
|
|
196
201
|
const html = errorTemplate({
|
|
197
202
|
error,
|
|
198
203
|
description: errorDescription
|
|
@@ -201,25 +206,89 @@ export function createOAuthCallbackHandler(config = {}) {
|
|
|
201
206
|
}
|
|
202
207
|
// Validate required parameters
|
|
203
208
|
if (!code || !stateParam) {
|
|
204
|
-
console.error('[OAuth]
|
|
209
|
+
console.error('[OAuth] 🔒 SECURITY EVENT: Missing required parameters:', {
|
|
210
|
+
hasCode: !!code,
|
|
211
|
+
hasState: !!stateParam,
|
|
212
|
+
timestamp: new Date().toISOString(),
|
|
213
|
+
eventType: 'oauth_validation_failed',
|
|
214
|
+
reason: 'missing_parameters'
|
|
215
|
+
});
|
|
205
216
|
const html = errorTemplate({
|
|
206
217
|
error: 'invalid_request',
|
|
207
218
|
description: 'Missing authorization code or state parameter'
|
|
208
219
|
});
|
|
209
220
|
return c.html(html, 400);
|
|
210
221
|
}
|
|
211
|
-
//
|
|
222
|
+
// ✅ CSRF Protection: Retrieve and validate state from secure storage
|
|
212
223
|
let state;
|
|
213
|
-
|
|
214
|
-
|
|
224
|
+
let stateData = null;
|
|
225
|
+
if (oauthSecurityService) {
|
|
226
|
+
// Use secure state validation (CSRF-protected)
|
|
227
|
+
try {
|
|
228
|
+
stateData = await oauthSecurityService.getOAuthState(stateParam);
|
|
229
|
+
if (!stateData) {
|
|
230
|
+
console.error('[OAuth] 🔒 SECURITY EVENT: State validation failed - state not found or expired:', {
|
|
231
|
+
stateParam: stateParam.substring(0, 20) + '...',
|
|
232
|
+
timestamp: new Date().toISOString(),
|
|
233
|
+
eventType: 'csrf_protection_failed',
|
|
234
|
+
reason: 'state_not_found_or_expired'
|
|
235
|
+
});
|
|
236
|
+
const html = errorTemplate({
|
|
237
|
+
error: 'invalid_state',
|
|
238
|
+
description: 'Invalid or expired state parameter. This may be a CSRF attack or the authorization request has expired.'
|
|
239
|
+
});
|
|
240
|
+
return c.html(html, 400);
|
|
241
|
+
}
|
|
242
|
+
// Extract state from stored data
|
|
243
|
+
state = {
|
|
244
|
+
project_id: stateData.project_id,
|
|
245
|
+
agent_did: stateData.agent_did,
|
|
246
|
+
session_id: stateData.session_id,
|
|
247
|
+
delegation_id: stateData.delegation_id,
|
|
248
|
+
};
|
|
249
|
+
console.log('[OAuth] 🔒 SECURITY EVENT: State validated successfully:', {
|
|
250
|
+
projectId: state.project_id,
|
|
251
|
+
agentDid: state.agent_did.substring(0, 20) + '...',
|
|
252
|
+
sessionId: state.session_id?.substring(0, 20) + '...',
|
|
253
|
+
timestamp: new Date().toISOString(),
|
|
254
|
+
eventType: 'csrf_protection_success',
|
|
255
|
+
stateStoredAt: stateData.storedAt
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
console.error('[OAuth] 🔒 SECURITY EVENT: State validation error:', {
|
|
260
|
+
error: err instanceof Error ? err.message : String(err),
|
|
261
|
+
stateParam: stateParam.substring(0, 20) + '...',
|
|
262
|
+
timestamp: new Date().toISOString(),
|
|
263
|
+
eventType: 'csrf_protection_error',
|
|
264
|
+
reason: 'validation_exception'
|
|
265
|
+
});
|
|
266
|
+
const html = errorTemplate({
|
|
267
|
+
error: 'invalid_state',
|
|
268
|
+
description: 'Failed to validate state parameter'
|
|
269
|
+
});
|
|
270
|
+
return c.html(html, 400);
|
|
271
|
+
}
|
|
215
272
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
273
|
+
else {
|
|
274
|
+
// Fallback: Decode state parameter directly (less secure, but backward compatible)
|
|
275
|
+
console.warn('[OAuth] ⚠️ SECURITY WARNING: OAuthSecurityService not provided, using insecure state decoding');
|
|
276
|
+
try {
|
|
277
|
+
state = JSON.parse(atob(stateParam));
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
console.error('[OAuth] 🔒 SECURITY EVENT: Failed to decode state:', {
|
|
281
|
+
error: err instanceof Error ? err.message : String(err),
|
|
282
|
+
timestamp: new Date().toISOString(),
|
|
283
|
+
eventType: 'oauth_validation_failed',
|
|
284
|
+
reason: 'state_decode_error'
|
|
285
|
+
});
|
|
286
|
+
const html = errorTemplate({
|
|
287
|
+
error: 'invalid_state',
|
|
288
|
+
description: 'Invalid state parameter'
|
|
289
|
+
});
|
|
290
|
+
return c.html(html, 400);
|
|
291
|
+
}
|
|
223
292
|
}
|
|
224
293
|
const { project_id, agent_did, session_id, delegation_id } = state;
|
|
225
294
|
// Validate session ID
|
|
@@ -231,11 +300,14 @@ export function createOAuthCallbackHandler(config = {}) {
|
|
|
231
300
|
});
|
|
232
301
|
return c.html(html, 400);
|
|
233
302
|
}
|
|
234
|
-
console.log('[OAuth] Processing authorization code exchange:', {
|
|
303
|
+
console.log('[OAuth] 🔒 SECURITY EVENT: Processing authorization code exchange:', {
|
|
235
304
|
projectId: project_id,
|
|
236
305
|
agentDid: agent_did.substring(0, 20) + '...',
|
|
237
|
-
sessionId: session_id,
|
|
238
|
-
delegationId: delegation_id
|
|
306
|
+
sessionId: session_id?.substring(0, 20) + '...',
|
|
307
|
+
delegationId: delegation_id,
|
|
308
|
+
timestamp: new Date().toISOString(),
|
|
309
|
+
eventType: 'oauth_code_exchange_start',
|
|
310
|
+
hasSecureState: !!oauthSecurityService
|
|
239
311
|
});
|
|
240
312
|
try {
|
|
241
313
|
// Exchange authorization code for delegation token
|
|
@@ -255,9 +327,13 @@ export function createOAuthCallbackHandler(config = {}) {
|
|
|
255
327
|
});
|
|
256
328
|
if (!tokenResponse.ok) {
|
|
257
329
|
const errorText = await tokenResponse.text();
|
|
258
|
-
console.error('[OAuth] Token exchange failed:', {
|
|
330
|
+
console.error('[OAuth] 🔒 SECURITY EVENT: Token exchange failed:', {
|
|
259
331
|
status: tokenResponse.status,
|
|
260
|
-
error: errorText
|
|
332
|
+
error: errorText.substring(0, 200),
|
|
333
|
+
projectId: project_id,
|
|
334
|
+
agentDid: agent_did.substring(0, 20) + '...',
|
|
335
|
+
timestamp: new Date().toISOString(),
|
|
336
|
+
eventType: 'oauth_token_exchange_failed'
|
|
261
337
|
});
|
|
262
338
|
const html = errorTemplate({
|
|
263
339
|
error: 'token_exchange_failed',
|
|
@@ -268,18 +344,26 @@ export function createOAuthCallbackHandler(config = {}) {
|
|
|
268
344
|
const tokenData = await tokenResponse.json();
|
|
269
345
|
// Validate token response
|
|
270
346
|
if (!tokenData.delegation_token) {
|
|
271
|
-
console.error('[OAuth]
|
|
347
|
+
console.error('[OAuth] 🔒 SECURITY EVENT: Invalid token response:', {
|
|
348
|
+
hasDelegationToken: !!tokenData.delegation_token,
|
|
349
|
+
responseKeys: Object.keys(tokenData),
|
|
350
|
+
projectId: project_id,
|
|
351
|
+
timestamp: new Date().toISOString(),
|
|
352
|
+
eventType: 'oauth_invalid_token_response'
|
|
353
|
+
});
|
|
272
354
|
const html = errorTemplate({
|
|
273
355
|
error: 'invalid_response',
|
|
274
356
|
description: 'Invalid token response from authorization server'
|
|
275
357
|
});
|
|
276
358
|
return c.html(html, 500);
|
|
277
359
|
}
|
|
278
|
-
console.log('[OAuth] Token exchange successful:', {
|
|
360
|
+
console.log('[OAuth] 🔒 SECURITY EVENT: Token exchange successful:', {
|
|
279
361
|
delegationId: tokenData.delegation_id,
|
|
280
362
|
sessionId: tokenData.session_id || session_id,
|
|
281
363
|
expiresIn: tokenData.expires_in,
|
|
282
|
-
scopes: tokenData.scopes
|
|
364
|
+
scopes: tokenData.scopes,
|
|
365
|
+
timestamp: new Date().toISOString(),
|
|
366
|
+
eventType: 'oauth_token_exchange_success'
|
|
283
367
|
});
|
|
284
368
|
// Phase 4 PR #3: Extract OAuth user info and link to User DID
|
|
285
369
|
let oauthIdentity = null;
|
|
@@ -320,16 +404,26 @@ export function createOAuthCallbackHandler(config = {}) {
|
|
|
320
404
|
// Set OAuth identity cookie for consent page
|
|
321
405
|
const cookieValue = encodeURIComponent(JSON.stringify(oauthIdentity));
|
|
322
406
|
c.header("Set-Cookie", `oauth_identity=${cookieValue}; HttpOnly; Secure; SameSite=Lax; Max-Age=604800; Path=/`);
|
|
323
|
-
console.log('[OAuth] OAuth identity linked and cookie set:', {
|
|
407
|
+
console.log('[OAuth] 🔒 SECURITY EVENT: OAuth identity linked and cookie set:', {
|
|
324
408
|
provider: oauthIdentity.provider,
|
|
325
409
|
subject: oauthIdentity.subject.substring(0, 20) + '...',
|
|
326
410
|
userDid: userDid.substring(0, 20) + '...',
|
|
411
|
+
sessionId: session_id?.substring(0, 20) + '...',
|
|
412
|
+
timestamp: new Date().toISOString(),
|
|
413
|
+
eventType: 'oauth_identity_linked',
|
|
414
|
+
cookieSet: true
|
|
327
415
|
});
|
|
328
416
|
}
|
|
329
417
|
}
|
|
330
418
|
catch (error) {
|
|
331
419
|
// OAuth linking errors are non-fatal - log but continue
|
|
332
|
-
console.error('[OAuth] Failed to link OAuth identity (non-fatal):',
|
|
420
|
+
console.error('[OAuth] 🔒 SECURITY EVENT: Failed to link OAuth identity (non-fatal):', {
|
|
421
|
+
error: error instanceof Error ? error.message : String(error),
|
|
422
|
+
sessionId: session_id?.substring(0, 20) + '...',
|
|
423
|
+
timestamp: new Date().toISOString(),
|
|
424
|
+
eventType: 'oauth_identity_linking_failed',
|
|
425
|
+
severity: 'warning'
|
|
426
|
+
});
|
|
333
427
|
}
|
|
334
428
|
}
|
|
335
429
|
// Store delegation token in KV if storage is configured
|
|
@@ -350,11 +444,15 @@ export function createOAuthCallbackHandler(config = {}) {
|
|
|
350
444
|
await delegationStorage.put(userAgentKey, tokenData.delegation_token, {
|
|
351
445
|
expirationTtl: ttl
|
|
352
446
|
});
|
|
353
|
-
console.log('[OAuth] Delegation token stored with user+agent DID:', {
|
|
354
|
-
key: userAgentKey,
|
|
447
|
+
console.log('[OAuth] 🔒 SECURITY EVENT: Delegation token stored with user+agent DID:', {
|
|
448
|
+
key: userAgentKey.substring(0, 50) + '...',
|
|
355
449
|
ttl,
|
|
356
450
|
agentDid: agent_did.substring(0, 20) + '...',
|
|
357
|
-
|
|
451
|
+
userDid: sessionUserDid.substring(0, 20) + '...',
|
|
452
|
+
delegationId: tokenData.delegation_id,
|
|
453
|
+
timestamp: new Date().toISOString(),
|
|
454
|
+
eventType: 'delegation_token_stored',
|
|
455
|
+
storageType: 'user_agent_scoped'
|
|
358
456
|
});
|
|
359
457
|
}
|
|
360
458
|
// Backward compatibility: Agent-only key (24 hour TTL)
|
|
@@ -362,11 +460,15 @@ export function createOAuthCallbackHandler(config = {}) {
|
|
|
362
460
|
await delegationStorage.put(legacyKey, tokenData.delegation_token, {
|
|
363
461
|
expirationTtl: 24 * 60 * 60 // 24 hours only
|
|
364
462
|
});
|
|
365
|
-
console.log('[OAuth] Delegation token stored with legacy agent key:', {
|
|
366
|
-
key: legacyKey,
|
|
463
|
+
console.log('[OAuth] 🔒 SECURITY EVENT: Delegation token stored with legacy agent key:', {
|
|
464
|
+
key: legacyKey.substring(0, 50) + '...',
|
|
367
465
|
ttl: 24 * 60 * 60,
|
|
368
466
|
agentDid: agent_did.substring(0, 20) + '...',
|
|
369
|
-
delegationId: tokenData.delegation_id
|
|
467
|
+
delegationId: tokenData.delegation_id,
|
|
468
|
+
timestamp: new Date().toISOString(),
|
|
469
|
+
eventType: 'delegation_token_stored',
|
|
470
|
+
storageType: 'legacy_agent_scoped',
|
|
471
|
+
warning: 'Legacy format - migrate to user+agent scoped tokens'
|
|
370
472
|
});
|
|
371
473
|
// Session cache for fast lookup (shorter TTL for performance)
|
|
372
474
|
await delegationStorage.put(sessionKey, JSON.stringify({
|
|
@@ -377,16 +479,25 @@ export function createOAuthCallbackHandler(config = {}) {
|
|
|
377
479
|
}), {
|
|
378
480
|
expirationTtl: Math.min(ttl, 1800) // 30 minutes or token TTL, whichever is shorter
|
|
379
481
|
});
|
|
380
|
-
console.log('[OAuth] Delegation token cached for session:', {
|
|
381
|
-
key: sessionKey,
|
|
482
|
+
console.log('[OAuth] 🔒 SECURITY EVENT: Delegation token cached for session:', {
|
|
483
|
+
key: sessionKey.substring(0, 50) + '...',
|
|
382
484
|
ttl: Math.min(ttl, 1800),
|
|
383
|
-
sessionId: session_id,
|
|
384
|
-
userDid: sessionUserDid,
|
|
485
|
+
sessionId: session_id?.substring(0, 20) + '...',
|
|
486
|
+
userDid: sessionUserDid?.substring(0, 20) + '...',
|
|
487
|
+
timestamp: new Date().toISOString(),
|
|
488
|
+
eventType: 'delegation_token_cached',
|
|
489
|
+
storageType: 'session_cache'
|
|
385
490
|
});
|
|
386
491
|
}
|
|
387
492
|
catch (storageError) {
|
|
388
493
|
// Storage errors are non-fatal - log but continue
|
|
389
|
-
console.error('[OAuth] Storage error (non-fatal):',
|
|
494
|
+
console.error('[OAuth] 🔒 SECURITY EVENT: Storage error (non-fatal):', {
|
|
495
|
+
error: storageError instanceof Error ? storageError.message : String(storageError),
|
|
496
|
+
sessionId: session_id?.substring(0, 20) + '...',
|
|
497
|
+
timestamp: new Date().toISOString(),
|
|
498
|
+
eventType: 'delegation_storage_error',
|
|
499
|
+
severity: 'warning'
|
|
500
|
+
});
|
|
390
501
|
}
|
|
391
502
|
}
|
|
392
503
|
// Return success page
|
|
@@ -399,7 +510,13 @@ export function createOAuthCallbackHandler(config = {}) {
|
|
|
399
510
|
return c.html(html);
|
|
400
511
|
}
|
|
401
512
|
catch (error) {
|
|
402
|
-
console.error('[OAuth] Unexpected error:',
|
|
513
|
+
console.error('[OAuth] 🔒 SECURITY EVENT: Unexpected error:', {
|
|
514
|
+
error: error instanceof Error ? error.message : String(error),
|
|
515
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
516
|
+
timestamp: new Date().toISOString(),
|
|
517
|
+
eventType: 'oauth_unexpected_error',
|
|
518
|
+
severity: 'error'
|
|
519
|
+
});
|
|
403
520
|
const html = errorTemplate({
|
|
404
521
|
error: 'internal_error',
|
|
405
522
|
description: error instanceof Error ? error.message : 'An unexpected error occurred'
|