╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │2227 // (not the user's id) that is stored as a file named by the token hex, whose │ │2228 // contents are the raw user_id bytes. authenticating a request is a lookup of│ │2229 // the cookie's token; logout deletes the file, revoking the session. there is│ │2159 -// deliberately no expiry — a session lives until logout. │ │2230 +// deliberately no expiry — a session lives until logout. form failures use │ │2231 +// files in the flash subdirectory that the redirected GET consumes. the │ │2232 +// directory is cleared whenever the server starts. │ │2233 pub const SessionStore = struct { │ │2234 io: std.Io, │ │2235 dir: std.Io.Dir, │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────╮ │2236 + flash_dir: std.Io.Dir, │ │2237 auto_login: ?[token_hex_len]u8, │ │2238 │ │2239 pub const token_hex_len = evt.event_id_size * 2;│ ╰──────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2241 pub fn init(io: std.Io, data_dir: std.Io.Dir) !SessionStore { │ │2242 try data_dir.createDirPath(io, "sessions"); │ │2243 const dir = try data_dir.openDir(io, "sessions", .{}); │ │2244 + errdefer dir.close(io); │ │2245 + try dir.deleteTree(io, "flash"); │ │2246 + try dir.createDirPath(io, "flash"); │ │2247 + const flash_dir = try dir.openDir(io, "flash", .{}); │ │2248 + errdefer flash_dir.close(io); │ │2249 const auto_login = blk: { │ │2250 const file = dir.openFile(io, auto_login_name, .{ .mode = .read_only }) catch break :blk null;│ │2251 defer file.close(io); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │2255 reader.interface.readSliceAll(&token) catch break :blk null; │ │2256 break :blk token; │ │2257 }; │ │2179 - return .{ .io = io, .dir = dir, .auto_login = auto_login }; │ │2258 + return .{ .io = io, .dir = dir, .flash_dir = flash_dir, .auto_login = auto_login };│ │2259 } │ │2260 │ │2261 pub fn deinit(self: SessionStore) void { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────╮ │2262 + self.flash_dir.close(self.io);│ │2263 self.dir.close(self.io); │ │2264 } │ │2265 │ ╰────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2272 const file = try self.dir.createFile(self.io, &token_hex, .{}); │ │2273 defer file.close(self.io); │ │2274 try file.writeStreamingAll(self.io, user_id); │ │2275 + return token_hex; │ │2276 + } │ │2277 + │ │2278 + fn createFlash(self: SessionStore, allocator: std.mem.Allocator, value: ui.Session.FormFeedback) ![token_hex_len]u8 {│ │2279 + var token: [evt.event_id_size]u8 = undefined; │ │2280 + self.io.random(&token); │ │2281 + const token_hex = std.fmt.bytesToHex(token, .lower); │ │2282 + const file = try self.flash_dir.createFile(self.io, &token_hex, .{}); │ │2283 + errdefer self.flash_dir.deleteFile(self.io, &token_hex) catch {}; │ │2284 + defer file.close(self.io); │ │2285 + │ │2286 + var json: std.Io.Writer.Allocating = .init(allocator); │ │2287 + defer json.deinit(); │ │2288 + try std.json.Stringify.value(value, .{}, &json.writer); │ │2289 + try file.writeStreamingAll(self.io, json.written()); │ │2290 return token_hex; │ │2291 + } │ │2292 + │ │2293 + fn takeFlash(self: SessionStore, allocator: std.mem.Allocator, token_hex: []const u8) ?ui.Session.FormFeedback { │ │2294 + if (!isToken(token_hex)) return null; │ │2295 + const file = self.flash_dir.openFile(self.io, token_hex, .{ .mode = .read_only }) catch return null; │ │2296 + defer self.flash_dir.deleteFile(self.io, token_hex) catch {}; │ │2297 + defer file.close(self.io); │ │2298 + │ │2299 + var storage: [4096]u8 = undefined; │ │2300 + var reader = file.reader(self.io, &storage); │ │2301 + const bytes = reader.interface.allocRemaining(allocator, .limited(512 * 1024)) catch return null; │ │2302 + return std.json.parseFromSliceLeaky(ui.Session.FormFeedback, allocator, bytes, .{}) catch null; │ │2303 } │ │2304 │ │2305 // resolve a cookie's token to its user_id. returns false (logged out) for a │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯