diff --git a/examples/userpass_session_auth/userpass_session_auth.zig b/examples/userpass_session_auth/userpass_session_auth.zig
index 4acdc0e..5de5e05 100644
--- a/examples/userpass_session_auth/userpass_session_auth.zig
+++ b/examples/userpass_session_auth/userpass_session_auth.zig
@@ -2,21 +2,26 @@ 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
+ // 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.SimpleEndpoint or
+// zap.Middleware and "hide" stuff like authenticators in there
var authenticator: Authenticator = undefined;
// the login page (embedded)
@@ -40,9 +45,13 @@ fn on_normal_page(r: zap.SimpleRequest) void {
fn on_logout(r: zap.SimpleRequest) 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
r.sendBody(
\\
\\ You are logged out!!!
+ \\
+ \\ Log back in
\\
) catch return;
}
@@ -50,12 +59,16 @@ fn on_logout(r: zap.SimpleRequest) void {
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
+ // 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", .{});
@@ -64,40 +77,38 @@ fn on_request(r: zap.SimpleRequest) void {
// 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
+ // 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")) {
- std.log.info("Auth OK for img", .{});
r.setContentTypeFromPath() catch unreachable;
r.sendBody(img) catch unreachable;
return;
}
- // aha! got redirected to /login
+ // 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 still authenticated for this
- // very request
+ // /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);
}
- // /logout can be shown since we're still authenticated for this
- // very request
+ // /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 still show the normal page
+ // any other paths will 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
+ // if there is no path but we're authenticated, so let's show
// the user something
return on_normal_page(r);
},
@@ -123,33 +134,28 @@ pub fn main() !void {
zap.enableDebugLog();
- // add a single user to our allowed users
+ // Usernames -> Passwords for the /login page
+ // ------------------------------------------
var userpass = Lookup.init(allocator);
defer userpass.deinit();
try userpass.put("zap", "awesome");
- // init our auth
+ // 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",
- .passwordParam = "password",
+ .usernameParam = "username", // form param name
+ .passwordParam = "password", // form param name
.loginPage = loginpath,
- .cookieName = "zap-session",
+ .cookieName = "zap-session", // cookie name for session
},
);
defer authenticator.deinit();
- // 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
diff --git a/src/http_auth.zig b/src/http_auth.zig
index eca59aa..c62d40d 100644
--- a/src/http_auth.zig
+++ b/src/http_auth.zig
@@ -343,8 +343,6 @@ pub const UserPassSessionAuthArgs = struct {
/// - `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
@@ -353,7 +351,7 @@ pub const UserPassSessionAuthArgs = struct {
/// -> 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 {
+pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool) type {
return struct {
allocator: std.mem.Allocator,
lookup: *Lookup,
@@ -389,16 +387,6 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
.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
- const token = try ret.createAndStoreSessionToken(kv.key_ptr.*, kv.value_ptr.*);
- allocator.free(token);
- }
- }
return ret;
}
@@ -417,25 +405,8 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
}
/// 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 = -1,
- })) {
- 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
+ // we erase the list of valid tokens server-side
if (r.setCookie(.{
.name = self.settings.cookieName,
.value = "invalid",
@@ -446,7 +417,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
zap.debug("logout cookie setting failed: {any}\n", .{err});
}
- r.parseCookies();
+ r.parseCookies(false);
// check for session cookie
if (r.getCookieStr(self.settings.cookieName, self.allocator, false)) |maybe_cookie| {
@@ -454,8 +425,14 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
defer cookie.deinit();
self.tokenLookupLock.lock();
defer self.tokenLookupLock.unlock();
- // if cookie is a valid session, remove it!
- _ = self.sessionTokens.remove(cookie);
+ if (self.sessionTokens.getKeyPtr(cookie.str)) |keyPtr| {
+ const keySlice = keyPtr.*;
+ // if cookie is a valid session, remove it!
+ _ = self.sessionTokens.remove(cookie.str);
+ // only now can we let go of the cookie str slice that
+ // was used as the key
+ self.allocator.free(keySlice);
+ }
}
} else |err| {
zap.debug("unreachable: UserPassSessionAuth.logout: {any}", .{err});
@@ -483,24 +460,14 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
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});
- }
+ 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 {
- 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});
- }
+ zap.debug("Auth: COKIE IS BAD!!!!: {s}\n", .{cookie.str});
}
}
} else |err| {
@@ -588,6 +555,11 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
var hasher = Hash.init(.{});
hasher.update(username);
hasher.update(password);
+ var buf: [16]u8 = undefined;
+ const time_nano = std.time.nanoTimestamp();
+ const timestampHex = try std.fmt.bufPrint(&buf, "{0x}", .{time_nano});
+ hasher.update(timestampHex);
+
var digest: [Hash.digest_length]u8 = undefined;
hasher.final(&digest);
const token: Token = std.fmt.bytesToHex(digest, .lower);
@@ -597,18 +569,11 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
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();
+ self.tokenLookupLock.lock();
+ defer self.tokenLookupLock.unlock();
- if (!self.sessionTokens.contains(token)) {
- try self.sessionTokens.put(try self.allocator.dupe(u8, token), {});
- }
- } else {
- if (!self.sessionTokens.contains(token)) {
- try self.sessionTokens.put(try self.allocator.dupe(u8, token), {});
- }
+ if (!self.sessionTokens.contains(token)) {
+ try self.sessionTokens.put(try self.allocator.dupe(u8, token), {});
}
return token;
}