╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │247 try request.respond("", .{ │ │248 .status = .see_other, │ │249 .extra_headers = &.{ │ │237 - .{ .name = "location", .value = "/" }, │ │250 + .{ .name = "location", .value = ui.RoutablePage.url(.home_users) },│ │251 .{ .name = "set-cookie", .value = cookie }, │ │252 }, │ │253 }); │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │256 try request.respond("", .{ │ │257 .status = .see_other, │ │258 .extra_headers = &.{ │ │246 - .{ .name = "location", .value = "/" }, │ │259 + .{ .name = "location", .value = ui.RoutablePage.url(.home_auth) }, │ │260 .{ .name = "set-cookie", .value = login_failure_cookie ++ "=unknown_user; Path=/; HttpOnly; SameSite=Strict" },│ │261 }, │ │262 }); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │265 try request.respond("", .{ │ │266 .status = .see_other, │ │267 .extra_headers = &.{ │ │255 - .{ .name = "location", .value = "/" }, │ │268 + .{ .name = "location", .value = ui.RoutablePage.url(.home_auth) }, │ │269 .{ .name = "set-cookie", .value = login_failure_cookie ++ "=wrong_password; Path=/; HttpOnly; SameSite=Strict" },│ │270 }, │ │271 }); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │280 try request.respond("", .{ │ │281 .status = .see_other, │ │282 .extra_headers = &.{ │ │270 - .{ .name = "location", .value = "/" }, │ │283 + .{ .name = "location", .value = ui.RoutablePage.url(.home_auth) }, │ │284 .{ .name = "set-cookie", .value = cookie_name ++ "=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0" },│ │285 }, │ │286 }); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │290 io: std.Io, │ │291 allocator: std.mem.Allocator, │ │292 admin_repo_path: []const u8, │ │280 - user_id_hex_opt: ?[]const u8, │ │281 - login_failure: ?ui.Home.Auth.Login.Failure, │ │293 + session_data: ui.SessionData, │ │294 ) ![]const u8 { │ │295 const template = (findEmbed("/index.html") orelse return error.MissingIndexAsset).body;│ │296 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │302 var page_arena = std.heap.ArenaAllocator.init(allocator); │ │303 defer page_arena.deinit(); │ │304 │ │293 - var session: ui.Session = .{}; │ │294 - if (user_id_hex_opt) |hex| { │ │295 - if (decodeHexAlloc(page_arena.allocator(), hex)) |bytes| { │ │296 - session.data.user_id = bytes; │ │297 - } else |_| {} │ │298 - } │ │299 - session.data.login_failure = login_failure; │ │305 + var session: ui.Session = .{ .data = session_data }; │ │306 │ │307 const snapshot: ui.Snapshot = .{ │ │308 .page = .{ .home = try .init(repo_opts, &page_arena, &repo, &session) },│ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │463 } │ │464 }.lt); │ │465 │ │460 - const action_url: []const u8 = if (session.data.user_id != null) "/logout" else "/login";│ │466 + // only the auth page hosts a form right now; on any other page we │ │467 + // emit nothing. on auth, the URL depends on whether there's already │ │468 + // an active session. │ │469 + const action_url: []const u8 = switch (session.data.current_page) { │ │470 + .home_auth => if (session.data.user_id != null) "/logout" else "/login", │ │471 + .home_users, .home_repos => return try out.toOwnedSlice(allocator), │ │472 + }; │ │473 │ │474 try out.appendSlice(allocator, "<form action=\""); │ │475 try out.appendSlice(allocator, action_url); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │582 return null; │ │583 } │ │584 │ │573 -fn decodeHexAlloc(allocator: std.mem.Allocator, hex: []const u8) ![]u8 { │ │574 - if (hex.len % 2 != 0) return error.InvalidHex; │ │575 - const bytes = try allocator.alloc(u8, hex.len / 2); │ │576 - errdefer allocator.free(bytes); │ │577 - _ = std.fmt.hexToBytes(bytes, hex) catch return error.InvalidHex; │ │578 - return bytes; │ │579 -} │ │580 - │ │585 fn parseFormField(allocator: std.mem.Allocator, body: []const u8, key: []const u8) !?[]u8 {│ │586 var iter = std.mem.splitScalar(u8, body, '&'); │ │587 while (iter.next()) |pair| { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯