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:
parent
b05004291e
commit
b8ca82f0fd
4 changed files with 201 additions and 3 deletions
|
@ -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
119
examples/app/auth.zig
Normal 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,
|
||||
});
|
||||
}
|
82
src/App.zig
82
src/App.zig
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue