//! //! Part of the Zap examples. //! //! Build me with `zig build userpass_session`. //! Run me with `zig build run-userpass_session`. //! const std = @import("std"); const zap = @import("zap"); // set default log level to .info and ZAP log level to .debug pub const std_options: std.Options = .{ .log_level = .info, .log_scope_levels = &[_]std.log.ScopeLevel{ .{ .scope = .zap, .level = .debug }, }, }; const Lookup = std.StringHashMap([]const u8); const auth_lock_pw_table = false; // see the source for more info const Authenticator = zap.Auth.UserPassSession( Lookup, // we may set this to true if we expect our username -> password map // to change. in that case the authenticator must lock the table for // every lookup auth_lock_pw_table, ); const loginpath = "/login"; // we bake the login page and its displayed image into the the executable const loginpage = @embedFile("html/login.html"); const img = @embedFile("./html/Ziggy_the_Ziguana.svg.png"); // global vars yeah! // in bigger projects, we'd probably make use of zap.Endpoint or // zap.Middleware and "hide" stuff like authenticators in there var authenticator: Authenticator = undefined; // the login page (embedded) fn on_login(r: zap.Request) !void { try r.sendBody(loginpage); } // the "normal page" fn on_normal_page(r: zap.Request) !void { zap.debug("on_normal_page()\n", .{}); try r.sendBody( \\ \\

Hello from ZAP!!!

\\

You are logged in!!! \\

logout
\\ ); } // the logged-out page fn on_logout(r: zap.Request) !void { zap.debug("on_logout()\n", .{}); authenticator.logout(&r); // note, the link below doesn't matter as the authenticator will send us // straight to the /login page try r.sendBody( \\ \\

You are logged out!!!

\\
\\

Log back in

\\ ); } fn on_request(r: zap.Request) !void { switch (authenticator.authenticateRequest(&r)) { .Handled => { // the authenticator handled the entire request for us. // that means: it re-directed to the /login page because of a // missing or invalid session cookie std.log.info("Auth FAILED -> authenticator handled it", .{}); return; }, // never returned by this type of authenticator .AuthFailed => unreachable, .AuthOK => { // the authenticator says it is ok to proceed as usual std.log.info("Auth OK", .{}); // dispatch to target path if (r.path) |p| { // used in the login page // note: our login page is /login // so, anything that starts with /login will not be touched by // the authenticator. Hence, we name the img for the /login // page: /login/Ziggy....png if (std.mem.startsWith(u8, p, "/login/Ziggy_the_Ziguana.svg.png")) { try r.setContentTypeFromPath(); try r.sendBody(img); return; } // aha! probably got redirected to /login if (std.mem.startsWith(u8, p, loginpath)) { std.log.info(" + for /login --> login page", .{}); return on_login(r); } // /logout can be shown since we're authenticated if (std.mem.startsWith(u8, p, "/logout")) { std.log.info(" + for /logout --> logout page", .{}); return on_logout(r); } // /stop can be executed, as we're authenticated if (std.mem.startsWith(u8, p, "/stop")) { std.log.info(" + for /stop --> logout page", .{}); zap.stop(); return on_logout(r); } // any other paths will show the normal page std.log.info(" + --> normal page", .{}); return on_normal_page(r); } // if there is no path but we're authenticated, so let's show // the user something return on_normal_page(r); }, } } pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true, }){}; // we start a block here so the defers will run before we call the gpa // to detect leaks { const allocator = gpa.allocator(); var listener = zap.HttpListener.init(.{ .port = 3000, .on_request = on_request, .log = true, .max_clients = 100000, }); try listener.listen(); // Usernames -> Passwords for the /login page // ------------------------------------------ var userpass = Lookup.init(allocator); defer userpass.deinit(); try userpass.put("zap", "awesome"); // init our authenticator. it will redirect all un-authenticated // requests to the /login page. on POST of correct username, password // pair, it will create an ephermal session token and let subsequent // requests that present the cookie through until, in our case: /logout. authenticator = try Authenticator.init( allocator, &userpass, .{ .usernameParam = "username", // form param name .passwordParam = "password", // form param name .loginPage = loginpath, .cookieName = "zap-session", // cookie name for session }, ); defer authenticator.deinit(); std.debug.print("Visit me on http://127.0.0.1:3000\n", .{}); std.debug.print(" Username: zap", .{}); std.debug.print(" Password: awesome", .{}); // start worker threads zap.start(.{ .threads = 2, .workers = 1, }); } // all defers should have run by now std.debug.print("\n\nSTOPPED!\n\n", .{}); const leaked = gpa.detectLeaks(); std.debug.print("Leaks detected: {}\n", .{leaked}); }