diff --git a/src/server.zig b/src/server.zig index 208d27e..80ff72c 100644 --- a/src/server.zig +++ b/src/server.zig @@ -5488,3 +5488,160 @@ test "server with auth enabled rejects unauthenticated requests, ping excepted" var client = try client_mod.Client.connect(allocator, server.boundAddress()); defer client.close(); + try client.ping(); + try std.testing.expectError(client_mod.ClientError.ServerUnauthorized, client.get(allocator, "k")); + try std.testing.expectError(client_mod.ClientError.ServerUnauthorized, client.set("k", "v")); + try std.testing.expectEqual(@as(u64, 2), server.unauthorizedTotal.get()); +} + +test "server rejects a bad password and accepts the correct one, then serves requests" { + const allocator = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = try tmp.dir.realpathAlloc(allocator, "."); + defer allocator.free(path); + + var db = try testDb(allocator, path); + defer db.close(); + const store = authmod.Store.init(&db); + try store.createUser(allocator, "alice", "hunter2", .readwrite, 16); + + const address = try std.net.Address.parseIp("127.0.0.1", 0); + var server = try Server.init(allocator, &db, address); + server.enableAuth(store); + try server.start(); + defer server.stop(); + + const client_mod = @import("client.zig"); + var client = try client_mod.Client.connect(allocator, server.boundAddress()); + defer client.close(); + + try std.testing.expectError(client_mod.ClientError.ServerUnauthorized, client.authenticate("alice", "wrong")); + try client.authenticate("alice", "hunter2"); + + try client.set("k", "v"); + const got = try client.get(allocator, "k"); + defer if (got) |g| allocator.free(g); + try std.testing.expectEqualStrings("v", got.?); +} + +test "server enforces readonly role: reads succeed, writes are forbidden" { + const allocator = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = try tmp.dir.realpathAlloc(allocator, "."); + defer allocator.free(path); + + var db = try testDb(allocator, path); + defer db.close(); + const store = authmod.Store.init(&db); + try store.createUser(allocator, "viewer", "pass", .readonly, 16); + + const address = try std.net.Address.parseIp("127.0.0.1", 0); + var server = try Server.init(allocator, &db, address); + server.enableAuth(store); + try server.start(); + defer server.stop(); + + const client_mod = @import("client.zig"); + var client = try client_mod.Client.connect(allocator, server.boundAddress()); + defer client.close(); + + try client.authenticate("viewer", "pass"); + const got = try client.get(allocator, "missing"); + try std.testing.expect(got == null); + try std.testing.expectError(client_mod.ClientError.ServerForbidden, client.set("k", "v")); + try std.testing.expectError(client_mod.ClientError.ServerForbidden, client.delete("k")); + try std.testing.expectError(client_mod.ClientError.ServerForbidden, client.compact()); + const txnEntries = [_]protocol.TxnOpEntry{ + .{ .verb = .put, .table = "accounts", .pk = .{ .int = 1 } }, + }; + try std.testing.expectError(client_mod.ClientError.ServerForbidden, client.txnOp(&txnEntries)); + try std.testing.expectEqual(@as(u64, 4), server.forbiddenTotal.get()); +} + +test "server without enableSessions returns no token from authenticate" { + const allocator = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = try tmp.dir.realpathAlloc(allocator, "."); + defer allocator.free(path); + + var db = try testDb(allocator, path); + defer db.close(); + const store = authmod.Store.init(&db); + try store.createUser(allocator, "alice", "hunter2", .readwrite, 16); + + const address = try std.net.Address.parseIp("127.0.0.1", 0); + var server = try Server.init(allocator, &db, address); + server.enableAuth(store); + try server.start(); + defer server.stop(); + + const client_mod = @import("client.zig"); + var client = try client_mod.Client.connect(allocator, server.boundAddress()); + defer client.close(); + + try client.authenticate("alice", "hunter2"); + try std.testing.expect(client.sessionToken() == null); +} + +test "a session token from one connection authenticates a brand new connection without a password" { + const allocator = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = try tmp.dir.realpathAlloc(allocator, "."); + defer allocator.free(path); + + var db = try testDb(allocator, path); + defer db.close(); + const store = authmod.Store.init(&db); + try store.createUser(allocator, "alice", "hunter2", .readwrite, 16); + + var sessions = sessionmod.SessionStore.init(allocator); + defer sessions.deinit(); + + const address = try std.net.Address.parseIp("127.0.0.1", 0); + var server = try Server.init(allocator, &db, address); + server.enableAuth(store); + server.enableSessions(&sessions, 60 * std.time.ns_per_s); + try server.start(); + defer server.stop(); + + const client_mod = @import("client.zig"); + + var first = try client_mod.Client.connect(allocator, server.boundAddress()); + try first.authenticate("alice", "hunter2"); + const token = try allocator.dupe(u8, first.sessionToken().?); + defer allocator.free(token); + first.close(); + + // A fully independent second connection, never given the password. + var second = try client_mod.Client.connect(allocator, server.boundAddress()); + defer second.close(); + try std.testing.expectError(client_mod.ClientError.ServerUnauthorized, second.get(allocator, "k")); + try second.authenticateWithToken(token); + try second.set("k", "v"); + const got = try second.get(allocator, "k"); + defer if (got) |g| allocator.free(g); + try std.testing.expectEqualStrings("v", got.?); +} + +test "auth_token is rejected as unauthorized when sessions are not enabled" { + const allocator = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = try tmp.dir.realpathAlloc(allocator, "."); + defer allocator.free(path); + + var db = try testDb(allocator, path); + defer db.close(); + const store = authmod.Store.init(&db); + try store.createUser(allocator, "alice", "hunter2", .readwrite, 16); + + const address = try std.net.Address.parseIp("127.0.0.1", 0); + var server = try Server.init(allocator, &db, address); + server.enableAuth(store); + try server.start(); + defer server.stop(); +