Skip to content

Commit

Permalink
UserPassSession Authentication!!!
Browse files Browse the repository at this point in the history
  • Loading branch information
renerocksai committed May 9, 2023
1 parent 645db5c commit 340b1ae
Show file tree
Hide file tree
Showing 9 changed files with 560 additions and 1 deletion.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ Here's what works:
- **[cookies](examples/cookies/cookies.zig)**: a simple example sending
itself a cookie and responding with a session cookie.
- **[websockets](examples/websockets/)**: a simple websockets chat for the browser.
- **[Username/Password Session Authentication](./examples/userpass_session_auth/)**:
A convenience authenticator that redirects un-authenticated requests to a
login page and sends cookies containing session tokens based on
username/password pairs transmitted via POST request.


I'll continue wrapping more of facil.io's functionality and adding stuff to zap
Expand Down
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub fn build(b: *std.build.Builder) !void {
.{ .name = "http_params", .src = "examples/http_params/http_params.zig" },
.{ .name = "cookies", .src = "examples/cookies/cookies.zig" },
.{ .name = "websockets", .src = "examples/websockets/websockets.zig" },
.{ .name = "userpass_session", .src = "examples/userpass_session_auth/userpass_session_auth.zig" },
}) |excfg| {
const ex_name = excfg.name;
const ex_src = excfg.src;
Expand Down
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.{
.name = "zap",
.version = "0.0.16",
.version = "0.0.17",

.dependencies = .{
.@"facil.io" = .{
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
90 changes: 90 additions & 0 deletions examples/userpass_session_auth/html/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<html>
<head>
<style>
/* Bordered form */
form {
border: 3px solid #f1f1f1;
}

/* Full-width inputs */
input[type=text], input[type=password] {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
box-sizing: border-box;
}

/* Set a style for all buttons */
button {
background-color: #04AA6D;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
cursor: pointer;
width: 100%;
}

/* Add a hover effect for buttons */
button:hover {
opacity: 0.8;
}

/* Extra style for the cancel button (red) */
.cancelbtn {
width: auto;
padding: 10px 18px;
background-color: #f44336;
}

/* Center the avatar image inside this container */
.imgcontainer {
text-align: center;
margin: 24px 0 12px 0;
}

/* Avatar image */
img.avatar {
width: 40%;
border-radius: 50%;
}

/* Add padding to containers */
.container {
padding: 16px;
}


/* Change styles for span and cancel button on extra small screens */
@media screen and (max-width: 300px) {
span.psw {
display: block;
float: none;
}
.cancelbtn {
width: 100%;
}
}
</style>
</head>
<body>
<form action="normal_page" method="post"> <!-- we post directly to the page we want to display if login is successful-->
<div class="imgcontainer">
<img src="/login/Ziggy_the_Ziguana.svg.png" alt="Avatar" class="avatar">
</div>

<div class="container">
<label for="username"><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="username" required>

<label for="password"><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="password" required>

<button type="submit">Login</button>
</div>
</form>
</body>
</html>

147 changes: 147 additions & 0 deletions examples/userpass_session_auth/userpass_session_auth.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
const std = @import("std");
const zap = @import("zap");

const Lookup = std.StringHashMap([]const u8);
const auth_lock_token_table = false;
const auth_lock_pw_table = false;

// see the source for more info
const Authenticator = zap.UserPassSessionAuth(
Lookup,
auth_lock_pw_table, // we may set this to true if we expect our username -> password map to change
auth_lock_token_table, // we may set this to true to have session tokens deleted server-side on logout
);

const loginpath = "/login";
const loginpage = @embedFile("html/login.html");
const img = @embedFile("./html/Ziggy_the_Ziguana.svg.png");

// global vars yeah!
var authenticator: Authenticator = undefined;

// the login page (embedded)
fn on_login(r: zap.SimpleRequest) void {
r.sendBody(loginpage) catch return;
}

// the "normal page"
fn on_normal_page(r: zap.SimpleRequest) void {
zap.debug("on_normal_page()\n", .{});
r.sendBody(
\\ <html><body>
\\ <h1>Hello from ZAP!!!</h1>
\\ <p>You are logged in!!!</>
\\ <center><a href="/logout">logout</a></center>
\\ </body></html>
) catch return;
}

// the logged-out page
fn on_logout(r: zap.SimpleRequest) void {
zap.debug("on_logout()\n", .{});
authenticator.logout(&r);
r.sendBody(
\\ <html><body>
\\ <p>You are logged out!!!</p>
\\ </body></html>
) catch return;
}

fn on_request(r: zap.SimpleRequest) void {
switch (authenticator.authenticateRequest(&r)) {
.Handled => {
// the authenticator handled the entire request for us. probably
// a redirect to the login page
std.log.info("Authenticator handled it", .{});
return;
},
.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 /login/Ziggy....png
if (std.mem.startsWith(u8, p, "/login/Ziggy_the_Ziguana.svg.png")) {
std.log.info("Auth OK for img", .{});
r.setContentTypeFromPath() catch unreachable;
r.sendBody(img) catch unreachable;
return;
}

// aha! 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 still authenticated for this
// very request
if (std.mem.startsWith(u8, p, "/logout")) {
std.log.info(" + for /logout --> logout page", .{});
return on_logout(r);
}

// any other paths will still show the normal page
std.log.info(" + --> normal page", .{});
return on_normal_page(r);
}
// if there is no path, we're still 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,
}){};
var allocator = gpa.allocator();

var listener = zap.SimpleHttpListener.init(.{
.port = 3000,
.on_request = on_request,
.log = true,
.max_clients = 100000,
});
try listener.listen();

zap.enableDebugLog();

// add a single user to our allowed users
var userpass = Lookup.init(allocator);
try userpass.put("zap", "awesome");

// init our auth
authenticator = try Authenticator.init(
allocator,
&userpass,
.{
.usernameParam = "username",
.passwordParam = "password",
.loginPage = loginpath,
.cookieName = "zap-session",
},
);

// just some debug output: listing the session tokens the authenticator may
// have generated already (if auth_lock_token_table == false).
const lookup = authenticator.sessionTokens;
std.debug.print("\nauth token list len: {d}\n", .{lookup.count()});
var it = lookup.iterator();
while (it.next()) |item| {
std.debug.print(" {s}\n", .{item.key_ptr.*});
}

std.debug.print("Visit me on http://127.0.0.1:3000\n", .{});

// start worker threads
zap.start(.{
.threads = 2,
.workers = 2,
});
}
Loading

0 comments on commit 340b1ae

Please sign in to comment.