mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 15:14:08 +00:00
UserPassSession Authentication!!!
This commit is contained in:
parent
645db5c8d0
commit
340b1ae3a8
9 changed files with 560 additions and 1 deletions
|
@ -47,6 +47,10 @@ Here's what works:
|
||||||
- **[cookies](examples/cookies/cookies.zig)**: a simple example sending
|
- **[cookies](examples/cookies/cookies.zig)**: a simple example sending
|
||||||
itself a cookie and responding with a session cookie.
|
itself a cookie and responding with a session cookie.
|
||||||
- **[websockets](examples/websockets/)**: a simple websockets chat for the browser.
|
- **[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
|
I'll continue wrapping more of facil.io's functionality and adding stuff to zap
|
||||||
|
|
|
@ -53,6 +53,7 @@ pub fn build(b: *std.build.Builder) !void {
|
||||||
.{ .name = "http_params", .src = "examples/http_params/http_params.zig" },
|
.{ .name = "http_params", .src = "examples/http_params/http_params.zig" },
|
||||||
.{ .name = "cookies", .src = "examples/cookies/cookies.zig" },
|
.{ .name = "cookies", .src = "examples/cookies/cookies.zig" },
|
||||||
.{ .name = "websockets", .src = "examples/websockets/websockets.zig" },
|
.{ .name = "websockets", .src = "examples/websockets/websockets.zig" },
|
||||||
|
.{ .name = "userpass_session", .src = "examples/userpass_session_auth/userpass_session_auth.zig" },
|
||||||
}) |excfg| {
|
}) |excfg| {
|
||||||
const ex_name = excfg.name;
|
const ex_name = excfg.name;
|
||||||
const ex_src = excfg.src;
|
const ex_src = excfg.src;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.{
|
.{
|
||||||
.name = "zap",
|
.name = "zap",
|
||||||
.version = "0.0.16",
|
.version = "0.0.17",
|
||||||
|
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.@"facil.io" = .{
|
.@"facil.io" = .{
|
||||||
|
|
BIN
examples/userpass_session_auth/html/Ziggy_the_Ziguana.svg.png
Normal file
BIN
examples/userpass_session_auth/html/Ziggy_the_Ziguana.svg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 323 KiB |
90
examples/userpass_session_auth/html/login.html
Normal file
90
examples/userpass_session_auth/html/login.html
Normal file
|
@ -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
examples/userpass_session_auth/userpass_session_auth.zig
Normal file
147
examples/userpass_session_auth/userpass_session_auth.zig
Normal file
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
|
@ -300,3 +300,308 @@ pub fn BearerAuthMulti(comptime Lookup: type) type {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const UserPassSessionAuthArgs = struct {
|
||||||
|
/// username body parameter
|
||||||
|
usernameParam: []const u8,
|
||||||
|
/// password body parameter
|
||||||
|
passwordParam: []const u8,
|
||||||
|
/// redirect to this page if auth fails
|
||||||
|
loginPage: []const u8,
|
||||||
|
/// name of the cookie
|
||||||
|
cookieName: []const u8,
|
||||||
|
/// cookie max age in seconds; 0 -> session cookie
|
||||||
|
cookieMaxAge: u8 = 0,
|
||||||
|
/// redirect status code, defaults to 302 found
|
||||||
|
redirectCode: zap.StatusCode = .found,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// UserPassSessionAuth supports the following use case:
|
||||||
|
///
|
||||||
|
/// - checks every request: is it going to the login page? -> let the request through.
|
||||||
|
/// - else:
|
||||||
|
/// - checks every request for a session token in a cookie
|
||||||
|
/// - if there is no token, it checks for correct username and password body params
|
||||||
|
/// - if username and password are present and correct, it will create a session token,
|
||||||
|
/// create a response cookie containing the token, and carry on with the request
|
||||||
|
/// - else it will redirect to the login page
|
||||||
|
/// - if the session token is present and correct: it will let the request through
|
||||||
|
/// - else: it will redirect to the login page
|
||||||
|
///
|
||||||
|
/// Please note the implications of this simple approach: IF YOU REUSE "username"
|
||||||
|
/// and "password" body params for anything else in your application, then the
|
||||||
|
/// mechanisms described above will kick in. For that reason: please know what you're
|
||||||
|
/// doing.
|
||||||
|
///
|
||||||
|
/// See UserPassSessionAuthArgs:
|
||||||
|
/// - username & password param names can be defined by you
|
||||||
|
/// - session cookie name and max-age can be defined by you
|
||||||
|
/// - login page and redirect code (.302) can be defined by you
|
||||||
|
///
|
||||||
|
/// Comptime Parameters:
|
||||||
|
///
|
||||||
|
/// - `Lookup` must implement .get([]const u8) -> []const u8 for user password retrieval
|
||||||
|
/// - `lockedPwLookups` : if true, accessing the provided Lookup instance will be protected
|
||||||
|
/// by a Mutex. You can access the mutex yourself via the `passwordLookupLock`.
|
||||||
|
/// - `lockedTokenLookups` : if true, accessing the internal token table will be protected
|
||||||
|
/// by a Mutex. You can access the mutex yourself via the `passwordLookupLock`.
|
||||||
|
///
|
||||||
|
/// Note: In order to be quick, you can set lockedTokenLookups to false.
|
||||||
|
/// -> we generate it on init() and leave it static
|
||||||
|
/// -> there is no way to 100% log out apart from re-starting the server
|
||||||
|
/// -> because: we send a cookie to the browser that invalidates the session cookie
|
||||||
|
/// -> another browser program with the page still open would still be able to use
|
||||||
|
/// -> the session. Which is kindof OK, but not as cool as erasing the token
|
||||||
|
/// -> on the server side which immediately block all other browsers as well.
|
||||||
|
pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool, comptime lockedTokenLookups: bool) type {
|
||||||
|
return struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
lookup: *Lookup,
|
||||||
|
settings: UserPassSessionAuthArgs,
|
||||||
|
|
||||||
|
// TODO: cookie store per user
|
||||||
|
sessionTokens: SessionTokenMap,
|
||||||
|
passwordLookupLock: std.Thread.Mutex = .{},
|
||||||
|
tokenLookupLock: std.Thread.Mutex = .{},
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
const SessionTokenMap = std.StringHashMap(void);
|
||||||
|
const Hash = std.crypto.hash.sha2.Sha256;
|
||||||
|
|
||||||
|
const Token = [Hash.digest_length * 2]u8;
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
lookup: *Lookup,
|
||||||
|
args: UserPassSessionAuthArgs,
|
||||||
|
) !Self {
|
||||||
|
var ret: Self = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.settings = .{
|
||||||
|
.usernameParam = try allocator.dupe(u8, args.usernameParam),
|
||||||
|
.passwordParam = try allocator.dupe(u8, args.passwordParam),
|
||||||
|
.loginPage = try allocator.dupe(u8, args.loginPage),
|
||||||
|
.cookieName = try allocator.dupe(u8, args.cookieName),
|
||||||
|
.cookieMaxAge = args.cookieMaxAge,
|
||||||
|
.redirectCode = args.redirectCode,
|
||||||
|
},
|
||||||
|
.lookup = lookup,
|
||||||
|
.sessionTokens = SessionTokenMap.init(allocator),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lockedTokenLookups == false) {
|
||||||
|
// we populate on init and forbid logout()
|
||||||
|
var it = lookup.iterator();
|
||||||
|
while (it.next()) |kv| {
|
||||||
|
// we iterate over all usernames and passwords, create tokens,
|
||||||
|
// and memorize the tokens
|
||||||
|
_ = try ret.createAndStoreSessionToken(kv.key_ptr.*, kv.value_ptr.*);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check for session token cookie, remove the token from the valid tokens
|
||||||
|
/// Note: only valid if lockedTokenLookups == true
|
||||||
|
pub fn logout(self: *Self, r: *const zap.SimpleRequest) void {
|
||||||
|
if (lockedTokenLookups == false) {
|
||||||
|
if (r.setCookie(.{
|
||||||
|
.name = self.settings.cookieName,
|
||||||
|
.value = "invalid",
|
||||||
|
.max_age_s = self.settings.cookieMaxAge,
|
||||||
|
})) {
|
||||||
|
zap.debug("logout ok\n", .{});
|
||||||
|
} else |err| {
|
||||||
|
zap.debug("logout cookie setting failed: {any}\n", .{err});
|
||||||
|
}
|
||||||
|
// @compileLog("WARNING! If lockedTokenLookups==false, logout() cannot erase the token from its internal, server-side list of valid tokens");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
zap.debug("logout cookie setting failed\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are allowed to lock the table, we can erase the list of valid tokens server-side
|
||||||
|
if (r.setCookie(.{
|
||||||
|
.name = self.settings.cookieName,
|
||||||
|
.value = "invalid",
|
||||||
|
.max_age_s = self.settings.cookieMaxAge,
|
||||||
|
})) {
|
||||||
|
zap.debug("logout ok\n", .{});
|
||||||
|
} else |err| {
|
||||||
|
zap.debug("logout cookie setting failed: {any}\n", .{err});
|
||||||
|
}
|
||||||
|
|
||||||
|
r.parseCookies();
|
||||||
|
|
||||||
|
// check for session cookie
|
||||||
|
if (r.getCookieStr(self.settings.cookieName, self.allocator, false)) |maybe_cookie| {
|
||||||
|
if (maybe_cookie) |cookie| {
|
||||||
|
defer cookie.deinit();
|
||||||
|
self.tokenLookupLock.lock();
|
||||||
|
defer self.tokenLookupLock.unlock();
|
||||||
|
// if cookie is a valid session, remove it!
|
||||||
|
_ = self.sessionTokens.remove(cookie);
|
||||||
|
}
|
||||||
|
} else |err| {
|
||||||
|
zap.debug("unreachable: UserPassSessionAuth.logout: {any}", .{err});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *const Self) void {
|
||||||
|
self.allocator.free(self.settings.usernameParam);
|
||||||
|
self.allocator.free(self.settings.passwordParam);
|
||||||
|
self.allocator.free(self.settings.loginPage);
|
||||||
|
self.allocator.free(self.settings.cookieName);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _internal_authenticateRequest(self: *Self, r: *const zap.SimpleRequest) AuthResult {
|
||||||
|
// if we're requesting the login page, let the request through
|
||||||
|
if (r.path) |p| {
|
||||||
|
if (std.mem.startsWith(u8, p, self.settings.loginPage)) {
|
||||||
|
return .AuthOK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse body
|
||||||
|
r.parseBody() catch {
|
||||||
|
// zap.debug("warning: parseBody() failed in UserPassSessionAuth: {any}", .{err});
|
||||||
|
// this is not an error in case of e.g. gets with querystrings
|
||||||
|
};
|
||||||
|
|
||||||
|
r.parseCookies(false);
|
||||||
|
|
||||||
|
// check for session cookie
|
||||||
|
if (r.getCookieStr(self.settings.cookieName, self.allocator, false)) |maybe_cookie| {
|
||||||
|
if (maybe_cookie) |cookie| {
|
||||||
|
defer cookie.deinit();
|
||||||
|
// locked or unlocked token lookup
|
||||||
|
if (lockedTokenLookups) {
|
||||||
|
self.tokenLookupLock.lock();
|
||||||
|
defer self.tokenLookupLock.unlock();
|
||||||
|
if (self.sessionTokens.contains(cookie.str)) {
|
||||||
|
// cookie is a valid session!
|
||||||
|
zap.debug("Auth: COKIE IS OK!!!!: {s}\n", .{cookie.str});
|
||||||
|
return .AuthOK;
|
||||||
|
} else {
|
||||||
|
zap.debug("Auth: COKIE IS BAD!!!!: {s}\n", .{cookie.str});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (self.sessionTokens.contains(cookie.str)) {
|
||||||
|
// cookie is a valid session!
|
||||||
|
zap.debug("Auth: COKIE IS OK!!!!: {s}\n", .{cookie.str});
|
||||||
|
return .AuthOK;
|
||||||
|
} else {
|
||||||
|
zap.debug("Auth: COKIE IS BAD!!!!: {s}\n", .{cookie.str});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else |err| {
|
||||||
|
zap.debug("unreachable: could not check for cookie in UserPassSessionAuth: {any}", .{err});
|
||||||
|
}
|
||||||
|
|
||||||
|
// get params of username and password
|
||||||
|
if (r.getParamStr(self.settings.usernameParam, self.allocator, false)) |maybe_username| {
|
||||||
|
if (maybe_username) |*username| {
|
||||||
|
defer username.deinit();
|
||||||
|
if (r.getParamStr(self.settings.passwordParam, self.allocator, false)) |maybe_pw| {
|
||||||
|
if (maybe_pw) |*pw| {
|
||||||
|
defer pw.deinit();
|
||||||
|
|
||||||
|
// now check
|
||||||
|
const correct_pw_optional = brk: {
|
||||||
|
if (lockedPwLookups) {
|
||||||
|
self.passwordLookupLock.lock();
|
||||||
|
defer self.passwordLookupLock.unlock();
|
||||||
|
break :brk self.lookup.*.get(username.str);
|
||||||
|
} else {
|
||||||
|
break :brk self.lookup.*.get(username.str);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (correct_pw_optional) |correct_pw| {
|
||||||
|
if (std.mem.eql(u8, pw.str, correct_pw)) {
|
||||||
|
// create session token
|
||||||
|
if (self.createAndStoreSessionToken(username.str, pw.str)) |token| {
|
||||||
|
// now set the cookie header
|
||||||
|
if (r.setCookie(.{
|
||||||
|
.name = self.settings.cookieName,
|
||||||
|
.value = token,
|
||||||
|
.max_age_s = self.settings.cookieMaxAge,
|
||||||
|
})) {
|
||||||
|
return .AuthOK;
|
||||||
|
} else |err| {
|
||||||
|
zap.debug("could not set session token: {any}", .{err});
|
||||||
|
}
|
||||||
|
} else |err| {
|
||||||
|
zap.debug("could not create session token: {any}", .{err});
|
||||||
|
}
|
||||||
|
// errors with token don't mean the auth itself wasn't OK
|
||||||
|
return .AuthOK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else |err| {
|
||||||
|
zap.debug("getParamSt() for password failed in UserPassSessionAuth: {any}", .{err});
|
||||||
|
return .AuthFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else |err| {
|
||||||
|
zap.debug("getParamSt() for user failed in UserPassSessionAuth: {any}", .{err});
|
||||||
|
return .AuthFailed;
|
||||||
|
}
|
||||||
|
return .AuthFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn authenticateRequest(self: *Self, r: *const zap.SimpleRequest) AuthResult {
|
||||||
|
switch (self._internal_authenticateRequest(r)) {
|
||||||
|
.AuthOK => {
|
||||||
|
// username and pass are ok -> created token, set header, caller can continue
|
||||||
|
return .AuthOK;
|
||||||
|
},
|
||||||
|
// this does not happen, just for completeness
|
||||||
|
.Handled => return .Handled,
|
||||||
|
// auth failed -> redirect
|
||||||
|
.AuthFailed => {
|
||||||
|
// we need to redirect and return .Handled
|
||||||
|
self.redirect(r) catch |err| {
|
||||||
|
// we just give up
|
||||||
|
zap.debug("redirect() failed in UserPassSessionAuth: {any}", .{err});
|
||||||
|
};
|
||||||
|
return .Handled;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redirect(self: *Self, r: *const zap.SimpleRequest) !void {
|
||||||
|
try r.redirectTo(self.settings.loginPage, self.settings.redirectCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createSessionToken(self: *Self, username: []const u8, password: []const u8) ![]const u8 {
|
||||||
|
var hasher = Hash.init(.{});
|
||||||
|
hasher.update(username);
|
||||||
|
hasher.update(password);
|
||||||
|
var digest: [Hash.digest_length]u8 = undefined;
|
||||||
|
hasher.final(&digest);
|
||||||
|
const token: Token = std.fmt.bytesToHex(digest, .lower);
|
||||||
|
const token_str = try self.allocator.dupe(u8, token[0..token.len]);
|
||||||
|
return token_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createAndStoreSessionToken(self: *Self, username: []const u8, password: []const u8) ![]const u8 {
|
||||||
|
const token = try self.createSessionToken(username, password);
|
||||||
|
// put locked or not
|
||||||
|
if (lockedTokenLookups) {
|
||||||
|
self.tokenLookupLock.lock();
|
||||||
|
defer self.tokenLookupLock.unlock();
|
||||||
|
|
||||||
|
if (!self.sessionTokens.contains(token)) {
|
||||||
|
try self.sessionTokens.put(token, {});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!self.sessionTokens.contains(token)) {
|
||||||
|
try self.sessionTokens.put(token, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -97,6 +97,13 @@ pub const SimpleRequest = struct {
|
||||||
return self.setHeader("content-type", s);
|
return self.setHeader("content-type", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// redirect to path with status code 302 by default
|
||||||
|
pub fn redirectTo(self: *const Self, path: []const u8, code: ?_module.StatusCode) HttpError!void {
|
||||||
|
self.setStatus(if (code) |status| status else .found);
|
||||||
|
try self.setHeader("Location", path);
|
||||||
|
try self.sendBody("moved");
|
||||||
|
}
|
||||||
|
|
||||||
/// shows how to use the logger
|
/// shows how to use the logger
|
||||||
pub fn setContentTypeWithLogger(
|
pub fn setContentTypeWithLogger(
|
||||||
self: *const Self,
|
self: *const Self,
|
||||||
|
|
|
@ -7,4 +7,9 @@ endpoint
|
||||||
wrk
|
wrk
|
||||||
mustache
|
mustache
|
||||||
endpoint_auth
|
endpoint_auth
|
||||||
|
http_params
|
||||||
pkghash
|
pkghash
|
||||||
|
cookies
|
||||||
|
websockets
|
||||||
|
userpass_session
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue