╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │2000 self.data.enable_ansi = user.event.enable_ansi; │ │2001 } │ │2002 │ │2003 - // a removed account's sessions read as logged out │ │2004 - fn logOut(self: *Self) void { │ │2003 + // forget the logged-in user, on a logout or once their account is removed│ │2004 + pub fn logOut(self: *Self) void { │ │2005 self.data.user_id = null; │ │2006 self.data.user_name = null; │ │2007 } │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Auth/Logout.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │92 } │ │93 │ │94 fn logout(self: *View) !void { │ │95 - self.session.data.user_id = null; │ │96 - self.session.data.user_name = null; │ │95 + self.session.logOut(); │ │96 // leave the auth tab for the page it belongs to, where the web's │ │97 // /logout redirect also lands │ │98 try self.session.navigate(self.session.data.current_page.pageRoot());│ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │210 // accounts, so it is always logged out. │ │211 var user_id_buf: [evt.event_id_size]u8 = undefined; │ │212 var user_id: ?[]const u8 = null; │ │213 + // the session token that named the user, so a stale one can be revoked│ │214 + var token_buf: [SessionStore.token_hex_len]u8 = undefined; │ │215 + var session_token: ?[]const u8 = null; │ │216 var form_arena = std.heap.ArenaAllocator.init(allocator); │ │217 defer form_arena.deinit(); │ │218 var form_feedback: ?ui.Session.FormFeedback = null; │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │229 user_id = blk: { │ │230 const token = getCookieValue(request, cookie_name) orelse break :blk null;│ │231 if (!server.session_store.lookup(token, &user_id_buf)) break :blk null; │ │232 + session_token = token; │ │233 break :blk user_id_buf[0..evt.event_id_size]; │ │234 }; │ │235 // whoever seeded the store may have left a session to claim │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │236 if (user_id == null) { │ │233 - var token: [SessionStore.token_hex_len]u8 = undefined; │ │234 - if (server.session_store.autoLogin(&token) and server.session_store.lookup(&token, &user_id_buf)) { │ │235 - session_cookie = try std.fmt.bufPrint(&cookie_buf, session_cookie_fmt, .{token}); │ │237 + if (server.session_store.autoLogin(&token_buf) and server.session_store.lookup(&token_buf, &user_id_buf)) {│ │238 + session_cookie = try std.fmt.bufPrint(&cookie_buf, session_cookie_fmt, .{token_buf}); │ │239 + session_token = &token_buf; │ │240 user_id = user_id_buf[0..evt.event_id_size]; │ │241 } │ │242 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │269 .server => |server| .{ server.git_http_port, server.git_ssh_port, server.git_ssh_prefix },│ │270 .local => .{ null, null, "" }, │ │271 }; │ │268 - const html = renderIndexHtml(io, allocator, host, .{ │ │272 + const html = renderIndexHtml(io, allocator, host, session_token, .{ │ │273 .user_id = user_id, │ │274 .form_feedback = form_feedback, │ │275 .sync_failure = sync_failure, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │1766 io: std.Io, │ │1767 allocator: std.mem.Allocator, │ │1768 host: Host, │ │1769 + session_token: ?[]const u8, │ │1770 session_data: ui.Session.Data, │ │1771 ) ![]const u8 { │ │1772 const template = (findEmbed("/index.html") orelse return error.MissingIndexAsset).body;│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1785 repo_maybe = try rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = server.admin_repo_path });│ │1786 const repo = if (repo_maybe) |*repo| repo else unreachable; │ │1787 var session = try ui.Session.init(&page_arena, repo, session_data); │ │1788 + // the session dropped a user whose account was removed, so revoke │ │1789 + // the token that named them │ │1790 + if (session_token) |token| { │ │1791 + if (session.data.user_id == null) server.session_store.remove(token); │ │1792 + } │ │1793 // give the page builders filesystem access to the on-disk repos (a │ │1794 // sibling "repos" dir next to the admin repo) so the Repo page can │ │1795 // read its files. │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯