╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │142 .server => |server| { │ │143 // the home page's new repo form, which the "/new" suffix below would claim │ │144 if (std.mem.eql(u8, path, "/repo/new")) return handleRepoNew(io, request, allocator, path, server.admin_repo_path, server.users_dir, server.session_store); │ │145 - const PostRoute = enum { login, logout, ansi, @"new-repo", new, edit, remove, open, close, resolve, publish, merge, squash, attach, undo, clear }; │ │145 + // likewise the home page's new user form │ │146 + if (std.mem.eql(u8, path, "/user/new")) return handleUserNew(io, request, allocator, path, server.admin_repo_path, server.users_dir, server.session_store); │ │147 + const PostRoute = enum { login, logout, ansi, @"new-repo", @"new-user", new, edit, remove, open, close, resolve, publish, merge, squash, attach, undo, clear };│ │148 inline for (@typeInfo(PostRoute).@"enum".fields) |field| { │ │149 const suffix = "/" ++ field.name; │ │150 if (std.mem.endsWith(u8, path, suffix)) { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │154 .logout => handleLogout(request, base, server.session_store), │ │155 .ansi => handleAnsi(io, request, allocator, base, server.admin_repo_path, server.users_dir, server.session_store), │ │156 .@"new-repo" => handleRepoNew(io, request, allocator, path, server.admin_repo_path, server.users_dir, server.session_store),│ │157 + .@"new-user" => handleUserNew(io, request, allocator, path, server.admin_repo_path, server.users_dir, server.session_store),│ │158 .new => handleNew(io, request, allocator, base, host), │ │159 .edit => handleEdit(io, request, allocator, base, host), │ │160 .remove => handleRemove(io, request, allocator, base, host), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │686 try request.respond("", .{ │ │687 .status = .see_other, │ │688 .extra_headers = &.{.{ .name = "location", .value = location }}, │ │689 + }); │ │690 +} │ │691 + │ │692 +// create a user, log them in, and redirect to their page. a failure goes │ │693 +// back to the new-user page that posted the form. │ │694 +fn handleUserNew( │ │695 + io: std.Io, │ │696 + request: *std.http.Server.Request, │ │697 + allocator: std.mem.Allocator, │ │698 + form_location: []const u8, │ │699 + admin_repo_path: []const u8, │ │700 + users_dir: []const u8, │ │701 + session_store: SessionStore, │ │702 +) !void { │ │703 + if (requestUserId(request, session_store) != null) { │ │704 + return request.respond("log out to create a user", .{ │ │705 + .status = .forbidden, │ │706 + .keep_alive = false, │ │707 + .extra_headers = &.{.{ .name = "content-type", .value = "text/plain" }}, │ │708 + }); │ │709 + } │ │710 + │ │711 + const body = try readFormBody(request, allocator); │ │712 + defer allocator.free(body); │ │713 + const name = (try parseFormField(allocator, body, "name")) orelse try allocator.dupe(u8, ""); │ │714 + defer allocator.free(name); │ │715 + const email = (try parseFormField(allocator, body, "email")) orelse try allocator.dupe(u8, ""); │ │716 + defer allocator.free(email); │ │717 + const password = (try parseFormField(allocator, body, "password")) orelse try allocator.dupe(u8, ""); │ │718 + defer allocator.free(password); │ │719 + const password_again = (try parseFormField(allocator, body, "password_again")) orelse try allocator.dupe(u8, ""); │ │720 + defer allocator.free(password_again); │ │721 + │ │722 + const user_id = blk: { │ │723 + var admin_repo = try rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = admin_repo_path }); │ │724 + defer admin_repo.deinit(io, allocator); │ │725 + break :blk evt.createUser(io, allocator, users_dir, &admin_repo, name, email, password, password_again) catch |err| { │ │726 + const failure = ui.Session.FormFeedback.UserFailure.fromError(err) orelse return err; │ │727 + return respondFormFailure(request, allocator, session_store, form_location, .{ .user = .{ .failure = failure, .fields = .{ .name = name, .email = email } } });│ │728 + }; │ │729 + }; │ │730 + │ │731 + const token = try session_store.create(&user_id); │ │732 + var cookie_buf: [256]u8 = undefined; │ │733 + const cookie = try std.fmt.bufPrint(&cookie_buf, session_cookie_fmt, .{token}); │ │734 + const location = try std.fmt.allocPrint(allocator, "/user/{s}", .{name}); │ │735 + defer allocator.free(location); │ │736 + try request.respond("", .{ │ │737 + .status = .see_other, │ │738 + .extra_headers = &.{ │ │739 + .{ .name = "location", .value = location }, │ │740 + .{ .name = "set-cookie", .value = cookie }, │ │741 + }, │ │742 }); │ │743 } │ │744 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯