1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 15:14:08 +00:00

zap.App.Create(Context).Endpoint.Authenticating -> zap.App is READY.

This commit is contained in:
renerocksai 2025-03-30 13:09:36 +02:00
parent b05004291e
commit b8ca82f0fd
4 changed files with 201 additions and 3 deletions

View file

@ -50,7 +50,8 @@ pub fn build(b: *std.Build) !void {
name: []const u8,
src: []const u8,
}{
.{ .name = "app", .src = "examples/app/main.zig" },
.{ .name = "app_basic", .src = "examples/app/basic.zig" },
.{ .name = "app_auth", .src = "examples/app/auth.zig" },
.{ .name = "hello", .src = "examples/hello/hello.zig" },
.{ .name = "https", .src = "examples/https/https.zig" },
.{ .name = "hello2", .src = "examples/hello2/hello2.zig" },

119
examples/app/auth.zig Normal file
View file

@ -0,0 +1,119 @@
const std = @import("std");
const zap = @import("zap");
const Allocator = std.mem.Allocator;
// The "Application Context"
const MyContext = struct {
bearer_token: []const u8,
};
// We reply with this
const HTTP_RESPONSE_TEMPLATE: []const u8 =
\\ <html><body>
\\ {s} from ZAP on {s} (token {s} == {s} : {s})!!!
\\ </body></html>
\\
;
// Our simple endpoint that will be wrapped by the authenticator
const MyEndpoint = struct {
// the slug
path: []const u8,
error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response,
fn get_bearer_token(r: zap.Request) []const u8 {
const auth_header = zap.Auth.extractAuthHeader(.Bearer, &r) orelse "Bearer (no token)";
return auth_header[zap.Auth.AuthScheme.Bearer.str().len..];
}
// authenticated GET requests go here
// we use the endpoint, the context, the arena, and try
pub fn get(ep: *MyEndpoint, arena: Allocator, context: *MyContext, r: zap.Request) !void {
const used_token = get_bearer_token(r);
const response = try std.fmt.allocPrint(
arena,
HTTP_RESPONSE_TEMPLATE,
.{ "Hello", ep.path, used_token, context.bearer_token, "OK" },
);
r.setStatus(.ok);
try r.sendBody(response);
}
// we also catch the unauthorized callback
// we use the endpoint, the context, the arena, and try
pub fn unauthorized(ep: *MyEndpoint, arena: Allocator, context: *MyContext, r: zap.Request) !void {
r.setStatus(.unauthorized);
const used_token = get_bearer_token(r);
const response = try std.fmt.allocPrint(
arena,
HTTP_RESPONSE_TEMPLATE,
.{ "UNAUTHORIZED", ep.path, used_token, context.bearer_token, "NOT OK" },
);
try r.sendBody(response);
}
// not implemented, don't care
pub fn post(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn put(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn delete(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn patch(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn options(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
};
pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{
// just to be explicit
.thread_safe = true,
}) = .{};
defer std.debug.print("\n\nLeaks detected: {}\n\n", .{gpa.deinit() != .ok});
const allocator = gpa.allocator();
// our global app context
var my_context: MyContext = .{ .bearer_token = "ABCDEFG" }; // ABCDEFG is our Bearer token
// our global app that holds the context
// App is the type
// app is the instance
const App = zap.App.Create(MyContext);
var app = try App.init(allocator, &my_context, .{});
defer app.deinit();
// create mini endpoint
var ep: MyEndpoint = .{
.path = "/test",
};
// create authenticator, use token from context
const Authenticator = zap.Auth.BearerSingle; // Simple Authenticator that uses a single bearer token
var authenticator = try Authenticator.init(allocator, my_context.bearer_token, null);
defer authenticator.deinit();
// create authenticating endpoint by combining endpoint and authenticator
const BearerAuthEndpoint = App.Endpoint.Authenticating(MyEndpoint, Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
// make the authenticating endpoint known to the app
try app.register(&auth_ep);
// listen
try app.listen(.{
.interface = "0.0.0.0",
.port = 3000,
});
std.debug.print(
\\ Run the following:
\\
\\ curl http://localhost:3000/test -i -H "Authorization: Bearer ABCDEFG" -v
\\ curl http://localhost:3000/test -i -H "Authorization: Bearer invalid" -v
\\
\\ and see what happens
\\
, .{});
// start worker threads
zap.start(.{
.threads = 2,
.workers = 1,
});
}

View file

@ -14,10 +14,11 @@ const RwLock = Thread.RwLock;
const zap = @import("zap.zig");
const Request = zap.Request;
const HttpListener = zap.HttpListener;
const ErrorStrategy = zap.Endpoint.ErrorStrategy;
pub const AppOpts = struct {
/// ErrorStrategy for (optional) request handler if no endpoint matches
default_error_strategy: zap.Endpoint.ErrorStrategy = .log_to_console,
default_error_strategy: ErrorStrategy = .log_to_console,
arena_retain_capacity: usize = 16 * 1024 * 1024,
};
@ -137,7 +138,7 @@ pub fn Create(comptime Context: type) type {
}
if (@hasField(T, "error_strategy")) {
if (@FieldType(T, "error_strategy") != zap.Endpoint.ErrorStrategy) {
if (@FieldType(T, "error_strategy") != ErrorStrategy) {
@compileError(@typeName(@FieldType(T, "error_strategy")) ++ " has wrong type, expected: zap.Endpoint.ErrorStrategy");
}
} else {
@ -164,6 +165,83 @@ pub fn Create(comptime Context: type) type {
}
}
}
/// Wrap an endpoint with an Authenticator
pub fn Authenticating(EndpointType: type, Authenticator: type) type {
return struct {
authenticator: *Authenticator,
ep: *EndpointType,
path: []const u8,
error_strategy: ErrorStrategy,
const AuthenticatingEndpoint = @This();
/// Init the authenticating endpoint. Pass in a pointer to the endpoint
/// you want to wrap, and the Authenticator that takes care of authenticating
/// requests.
pub fn init(e: *EndpointType, authenticator: *Authenticator) AuthenticatingEndpoint {
return .{
.authenticator = authenticator,
.ep = e,
.path = e.path,
.error_strategy = e.error_strategy,
};
}
/// Authenticates GET requests using the Authenticator.
pub fn get(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.get(arena, context, request),
.Handled => {},
};
}
/// Authenticates POST requests using the Authenticator.
pub fn post(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.post(arena, context, request),
.Handled => {},
};
}
/// Authenticates PUT requests using the Authenticator.
pub fn put(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.put(arena, context, request),
.Handled => {},
};
}
/// Authenticates DELETE requests using the Authenticator.
pub fn delete(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.delete(arena, context, request),
.Handled => {},
};
}
/// Authenticates PATCH requests using the Authenticator.
pub fn patch(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.patch(arena, context, request),
.Handled => {},
};
}
/// Authenticates OPTIONS requests using the Authenticator.
pub fn options(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.put(arena, context, request),
.Handled => {},
};
}
};
}
};
pub const ListenerSettings = struct {