mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-30 15:41:48 +00:00
Merge pull request #960 from lightpanda-io/auth-challenge
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
auth required interception
This commit is contained in:
@@ -474,12 +474,16 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
||||
self.cdp.browser.notification.unregister(.http_response_header_done, self);
|
||||
}
|
||||
|
||||
pub fn fetchEnable(self: *Self) !void {
|
||||
pub fn fetchEnable(self: *Self, authRequests: bool) !void {
|
||||
try self.cdp.browser.notification.register(.http_request_intercept, self, onHttpRequestIntercept);
|
||||
if (authRequests) {
|
||||
try self.cdp.browser.notification.register(.http_request_auth_required, self, onHttpRequestAuthRequired);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetchDisable(self: *Self) void {
|
||||
self.cdp.browser.notification.unregister(.http_request_intercept, self);
|
||||
self.cdp.browser.notification.unregister(.http_request_auth_required, self);
|
||||
}
|
||||
|
||||
pub fn onPageRemove(ctx: *anyopaque, _: Notification.PageRemove) !void {
|
||||
@@ -545,6 +549,12 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
||||
try gop.value_ptr.appendSlice(arena, try arena.dupe(u8, msg.data));
|
||||
}
|
||||
|
||||
pub fn onHttpRequestAuthRequired(ctx: *anyopaque, data: *const Notification.RequestAuthRequired) !void {
|
||||
const self: *Self = @alignCast(@ptrCast(ctx));
|
||||
defer self.resetNotificationArena();
|
||||
try @import("domains/fetch.zig").requestAuthRequired(self.notification_arena, self, data);
|
||||
}
|
||||
|
||||
fn resetNotificationArena(self: *Self) void {
|
||||
defer _ = self.cdp.notification_arena.reset(.{ .retain_with_limit = 1024 * 64 });
|
||||
}
|
||||
|
||||
@@ -32,12 +32,14 @@ pub fn processMessage(cmd: anytype) !void {
|
||||
continueRequest,
|
||||
failRequest,
|
||||
fulfillRequest,
|
||||
continueWithAuth,
|
||||
}, cmd.input.action) orelse return error.UnknownMethod;
|
||||
|
||||
switch (action) {
|
||||
.disable => return disable(cmd),
|
||||
.enable => return enable(cmd),
|
||||
.continueRequest => return continueRequest(cmd),
|
||||
.continueWithAuth => return continueWithAuth(cmd),
|
||||
.failRequest => return failRequest(cmd),
|
||||
.fulfillRequest => return fulfillRequest(cmd),
|
||||
}
|
||||
@@ -144,12 +146,8 @@ fn enable(cmd: anytype) !void {
|
||||
return cmd.sendResult(null, .{});
|
||||
}
|
||||
|
||||
if (params.handleAuthRequests) {
|
||||
log.warn(.cdp, "not implemented", .{ .feature = "Fetch.enable handleAuthRequests is not supported yet" });
|
||||
}
|
||||
|
||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||
try bc.fetchEnable();
|
||||
try bc.fetchEnable(params.handleAuthRequests);
|
||||
|
||||
return cmd.sendResult(null, .{});
|
||||
}
|
||||
@@ -276,6 +274,63 @@ fn continueRequest(cmd: anytype) !void {
|
||||
return cmd.sendResult(null, .{});
|
||||
}
|
||||
|
||||
// https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#type-AuthChallengeResponse
|
||||
const AuthChallengeResponse = enum {
|
||||
Default,
|
||||
CancelAuth,
|
||||
ProvideCredentials,
|
||||
};
|
||||
|
||||
fn continueWithAuth(cmd: anytype) !void {
|
||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||
const params = (try cmd.params(struct {
|
||||
requestId: []const u8, // "INTERCEPT-{d}"
|
||||
authChallengeResponse: struct {
|
||||
response: AuthChallengeResponse,
|
||||
username: []const u8 = "",
|
||||
password: []const u8 = "",
|
||||
},
|
||||
})) orelse return error.InvalidParams;
|
||||
|
||||
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||
|
||||
var intercept_state = &bc.intercept_state;
|
||||
const request_id = try idFromRequestId(params.requestId);
|
||||
const transfer = intercept_state.remove(request_id) orelse return error.RequestNotFound;
|
||||
|
||||
log.debug(.cdp, "request intercept", .{
|
||||
.state = "continue with auth",
|
||||
.id = transfer.id,
|
||||
.response = params.authChallengeResponse.response,
|
||||
});
|
||||
|
||||
if (params.authChallengeResponse.response != .ProvideCredentials) {
|
||||
transfer.abortAuthChallenge();
|
||||
return cmd.sendResult(null, .{});
|
||||
}
|
||||
|
||||
// cancel the request, deinit the transfer on error.
|
||||
errdefer transfer.abortAuthChallenge();
|
||||
|
||||
// restart the request with the provided credentials.
|
||||
const arena = transfer.arena.allocator();
|
||||
transfer.updateCredentials(
|
||||
try std.fmt.allocPrintZ(arena, "{s}:{s}", .{
|
||||
params.authChallengeResponse.username,
|
||||
params.authChallengeResponse.password,
|
||||
}),
|
||||
);
|
||||
|
||||
transfer.reset();
|
||||
try bc.cdp.browser.http_client.process(transfer);
|
||||
|
||||
if (intercept_state.empty()) {
|
||||
page.request_intercepted = false;
|
||||
}
|
||||
|
||||
return cmd.sendResult(null, .{});
|
||||
}
|
||||
|
||||
fn fulfillRequest(cmd: anytype) !void {
|
||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||
|
||||
@@ -346,6 +401,50 @@ fn failRequest(cmd: anytype) !void {
|
||||
return cmd.sendResult(null, .{});
|
||||
}
|
||||
|
||||
pub fn requestAuthRequired(arena: Allocator, bc: anytype, intercept: *const Notification.RequestAuthRequired) !void {
|
||||
// unreachable because we _have_ to have a page.
|
||||
const session_id = bc.session_id orelse unreachable;
|
||||
const target_id = bc.target_id orelse unreachable;
|
||||
const page = bc.session.currentPage() orelse unreachable;
|
||||
|
||||
// We keep it around to wait for modifications to the request.
|
||||
// NOTE: we assume whomever created the request created it with a lifetime of the Page.
|
||||
// TODO: What to do when receiving replies for a previous page's requests?
|
||||
|
||||
const transfer = intercept.transfer;
|
||||
try bc.intercept_state.put(transfer);
|
||||
|
||||
const challenge = transfer._auth_challenge orelse return error.NullAuthChallenge;
|
||||
|
||||
try bc.cdp.sendEvent("Fetch.authRequired", .{
|
||||
.requestId = try std.fmt.allocPrint(arena, "INTERCEPT-{d}", .{transfer.id}),
|
||||
.request = network.TransferAsRequestWriter.init(transfer),
|
||||
.frameId = target_id,
|
||||
.resourceType = switch (transfer.req.resource_type) {
|
||||
.script => "Script",
|
||||
.xhr => "XHR",
|
||||
.document => "Document",
|
||||
},
|
||||
.authChallenge = .{
|
||||
.source = if (challenge.source == .server) "Server" else "Proxy",
|
||||
.origin = "", // TODO get origin, could be the proxy address for example.
|
||||
.scheme = if (challenge.scheme == .digest) "digest" else "basic",
|
||||
.realm = challenge.realm,
|
||||
},
|
||||
.networkId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}),
|
||||
}, .{ .session_id = session_id });
|
||||
|
||||
log.debug(.cdp, "request auth required", .{
|
||||
.state = "paused",
|
||||
.id = transfer.id,
|
||||
.url = transfer.uri,
|
||||
});
|
||||
// Await continueWithAuth
|
||||
|
||||
intercept.wait_for_interception.* = true;
|
||||
page.request_intercepted = true;
|
||||
}
|
||||
|
||||
// Get u64 from requestId which is formatted as: "INTERCEPT-{d}"
|
||||
fn idFromRequestId(request_id: []const u8) !u64 {
|
||||
if (!std.mem.startsWith(u8, request_id, "INTERCEPT-")) {
|
||||
|
||||
Reference in New Issue
Block a user