srctree

Gregory Mullen parent 983db413 77c4aaac
implemented functional Auth via mTLS

src/auth.zig added: 110, removed: 25, total 85
@@ -8,9 +8,10 @@ pub const AuthN = @import("authentication.zig");
pub const User = @import("auth/user.zig");
 
pub const Error = error{
UnknownUser,
Unauthenticated,
InvalidAuth,
NotProvided,
Unauthenticated,
UnknownUser,
};
 
/// Fails closed: the provider used may return an error which will be caught and
@@ -26,14 +27,31 @@ pub const Error = error{
//}
 
pub const MTLS = struct {
pub fn provider(mtls: *MTLS) Provider {
return Provider{
.ctx = mtls,
.vtable = .{
.valid = validPtr,
.lookup_user = lookupUserPtr,
},
};
base: ?Provider = null,
 
pub fn authenticatePtr(ptr: *anyopaque, headers: *const Headers) Error!User {
const self: *MTLS = @ptrCast(@alignCast(ptr));
return self.authenticate(headers);
}
 
pub fn authenticate(mtls: *MTLS, headers: *const Headers) Error!User {
var success: bool = false;
if (headers.get("MTLS_ENABLED")) |enabled| {
if (enabled.value_list.next) |_| return error.InvalidAuth;
if (std.mem.eql(u8, enabled.value_list.value, "SUCCESS")) {
success = true;
}
}
 
if (!success) return error.UnknownUser;
 
if (mtls.base) |base| {
if (headers.get("MTLS_FINGERPRINT")) |enabled| {
if (enabled.value_list.next != null) return error.InvalidAuth;
return base.lookupUser(enabled.value_list.value);
}
}
return .{ .user_ptr = null };
}
 
pub fn valid(_: *MTLS, _: *const User) bool {
@@ -41,7 +59,7 @@ pub const MTLS = struct {
}
 
fn validPtr(ptr: *anyopaque, user: *const User) bool {
const self: *MTLS = @ptrCast(ptr);
const self: *MTLS = @ptrCast(@alignCast(ptr));
return self.valid(user);
}
 
@@ -50,14 +68,47 @@ pub const MTLS = struct {
}
 
pub fn lookupUserPtr(ptr: *anyopaque, user_id: []const u8) Error!User {
const self: *MTLS = @ptrCast(ptr);
const self: *MTLS = @ptrCast(@alignCast(ptr));
return self.lookupUser(user_id);
}
 
pub fn provider(mtls: *MTLS) Provider {
return Provider{
.ctx = mtls,
.vtable = .{
.authenticate = authenticatePtr,
.valid = validPtr,
.lookup_user = lookupUserPtr,
},
};
}
};
 
test MTLS {
//const a = std.testing.allocator;
const a = std.testing.allocator;
var mtls = MTLS{};
var provider = mtls.provider();
 
var headers = Headers.init(a);
defer headers.raze();
try headers.add("MTLS_ENABLED", "SUCCESS");
try headers.add("MTLS_FINGERPRINT", "LOLTOTALLYVALID");
 
const user = try provider.authenticate(&headers);
 
try std.testing.expectEqual(null, user.user_ptr);
 
try headers.add("MTLS_ENABLED", "SUCCESS");
const err = provider.authenticate(&headers);
try std.testing.expectError(error.InvalidAuth, err);
 
headers.raze();
headers = Headers.init(a);
 
try headers.add("MTLS_ENABLED", "FAILURE!");
const err2 = provider.authenticate(&headers);
try std.testing.expectError(error.UnknownUser, err2);
// TODO there's likely a few more error states we should validate;
}
 
pub const InvalidAuth = struct {
@@ -65,6 +116,7 @@ pub const InvalidAuth = struct {
return Provider{
.ctx = undefined,
.vtable = .{
.authenticate = null, // TODO write invalid
.valid = valid,
.lookup_user = lookupUser,
},
@@ -105,6 +157,7 @@ const TestingAuth = struct {
return .{
.ctx = self,
.vtable = .{
.authenticate = null,
.valid = null,
.lookup_user = lookupUserUntyped,
},
@@ -127,3 +180,5 @@ test Provider {
 
const std = @import("std");
const Allocator = std.mem.Allocator;
const Verse = @import("verse.zig");
const Headers = @import("headers.zig");
 
src/auth/provider.zig added: 110, removed: 25, total 85
@@ -6,30 +6,40 @@ vtable: VTable,
const Provider = @This();
 
pub const VTable = struct {
authenticate: ?AuthenticateFn,
lookup_user: ?LookupUserFn,
valid: ?ValidFn,
 
pub const LookupUserFn = *const fn (*const anyopaque, []const u8) Auth.Error!Auth.User;
pub const ValidFn = *const fn (*const anyopaque, *const User) bool;
pub const AuthenticateFn = *const fn (*anyopaque, *const Headers) Auth.Error!Auth.User;
pub const LookupUserFn = *const fn (*anyopaque, []const u8) Auth.Error!Auth.User;
pub const ValidFn = *const fn (*anyopaque, *const User) bool;
 
pub const DefaultEmpty = .{
.authenticate = null,
.lookup_user = null,
.valid = null,
};
};
 
pub fn authenticate(self: *const Provider, headers: *const Headers) Auth.Error!Auth.User {
if (self.vtable.authenticate) |func| {
return try func(self.ctx, headers);
} else return error.NotProvided;
}
 
pub fn valid(self: *const Provider, user: *const User) bool {
if (self.vtable.valid) |valid_fn| {
return valid_fn(self.ctx, user);
if (self.vtable.valid) |func| {
return func(self.ctx, user);
} else false;
}
 
/// TODO document the implications of non consttime function
pub fn lookupUser(self: *const Provider, user_id: []const u8) Auth.Error!Auth.User {
if (self.vtable.lookup_user) |lookup_fn| {
return try lookup_fn(self.ctx, user_id);
if (self.vtable.lookup_user) |func| {
return try func(self.ctx, user_id);
} else return error.NotProvided;
}
 
const Auth = @import("../auth.zig");
const Headers = @import("../headers.zig");
const User = @import("user.zig");
 
src/auth/user.zig added: 110, removed: 25, total 85
@@ -1,5 +1,5 @@
//! This is a default User provided by Verse. This is almost certainly not what
//! you want.
user_ptr: *anyopaque,
user_ptr: ?*anyopaque,
 
pub const User = @This();
 
src/headers.zig added: 110, removed: 25, total 85
@@ -8,6 +8,12 @@ pub const Header = struct {
value: []const u8,
};
 
/// Unstable API that may get removed
pub const HeaderList = struct {
name: []const u8,
value_list: *ValueList,
};
 
const ValueList = struct {
value: []const u8,
next: ?*ValueList = null,
@@ -58,6 +64,15 @@ pub fn add(h: *Headers, name: []const u8, value: []const u8) !void {
}
}
 
pub fn get(h: *const Headers, name: []const u8) ?HeaderList {
if (h.headers.get(name)) |header| {
return .{
.name = name,
.value_list = header,
};
} else return null;
}
 
/// Starting an iteration will lock the map pointers, callers must complete the
/// iteration, or manually unlock internal pointers. See also: Iterator.finish();
pub fn iterator(h: *Headers) Iterator {
 
src/verse.zig added: 110, removed: 25, total 85
@@ -26,6 +26,11 @@ uri: UriIter,
 
// TODO fix this unstable API
auth: Auth.Provider,
/// user is set to exactly what is provided directly by the active Auth.Provider.
/// It's possible for an Auth.Provider to return a User that is invalid.
/// Depending on the need for any given use, users should always verify the
/// validity in addition to the existence of this user field.
user: ?Auth.User = null,
/// The RouteData API is currently unstable, use with caution
route_data: RouteData,