diff options
Diffstat (limited to 'src/routes/handler-info.zig')
| -rw-r--r-- | src/routes/handler-info.zig | 201 |
1 files changed, 141 insertions, 60 deletions
diff --git a/src/routes/handler-info.zig b/src/routes/handler-info.zig index bac7612..7b744d2 100644 --- a/src/routes/handler-info.zig +++ b/src/routes/handler-info.zig @@ -2,24 +2,65 @@ const std = @import("std"); const Context = @import("context.zig"); const Storage = @import("../storage/root.zig"); +const Access = @import("access.zig").Access; + const log = std.log.scoped(.handler_info); -handler: *const fn (*Context) anyerror![]const u8, -needs_auth: bool, -method: std.http.Method, +const Self = @This(); + +const Handler = *const fn (*Context) anyerror![]const u8; + +get: ?Handler, +head: ?Handler, +post: ?Handler, +put: ?Handler, +delete: ?Handler, +connect: ?Handler, +options: ?Handler, +trace: ?Handler, +patch: ?Handler, +access: Access, + +inline fn handler_from_method(self: *const Self, method: std.http.Method) ?Handler { + return switch (method) { + .GET => self.get, + .HEAD => self.head, + .POST => self.post, + .PUT => self.put, + .DELETE => self.delete, + .CONNECT => self.connect, + .OPTIONS => self.options, + .TRACE => self.trace, + .PATCH => self.patch, + }; +} + +inline fn get_fingerprint(cookie: []const u8) []const u8 { + const start = std.mem.indexOf(u8, cookie, "fingerprint=") orelse return ""; + const end = std.mem.indexOf(u8, cookie[start + 12..], " ") orelse return cookie[start + 12..]; + return cookie[start..end + 1]; +} pub fn handle( - self: *const @This(), + self: *const Self, request: *std.http.Server.Request, storage: *Storage, allocator: std.mem.Allocator, ) !void { - if (request.head.method != self.method) { - return request.respond( - "{ \"error\": \"Bad Request\" }", - .{ .status = .bad_request } - ); - } + const handler = self.handler_from_method(request.head.method) orelse return request.respond( + "{ \"error\": \"Bad Request\" }", + .{ .status = .bad_request } + ); + + const cookie = cookie: { + var iterator = request.iterateHeaders(); + while (iterator.next()) |header| { + if (std.ascii.eqlIgnoreCase(header.name, "cookie")) { + break :cookie header.value; + } + } + break :cookie ""; + }; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); @@ -27,9 +68,28 @@ pub fn handle( .request = request, .storage = storage, .allocator = arena.allocator(), + .fingerprint = get_fingerprint(cookie), }; - const response = self.handler(&context) catch |err| { + const allowed = switch (self.access) { + .everyone => true, + .users => storage.sessions.get(storage, context.fingerprint) != null, + .admins => admin: { + if (storage.sessions.get(storage, context.fingerprint)) |session| { + break :admin session.info.is_admin; + } + break :admin false; + }, + }; + + if (!allowed) { + return request.respond( + "{ \"error\": \"Forbidden\" }", + .{ .status = .forbidden } + ); + } + + const response = handler(&context) catch |err| { const response, const status_code: std.http.Status = switch (err) { error.BadRequest => .{ "{ \"error\": \"Bad Request\" }", .bad_request }, error.Unauthorized => .{ "{ \"error\": \"Unauthorized\" }", .unauthorized }, @@ -44,70 +104,91 @@ pub fn handle( return request.respond(response, .{ .status = status_code }); }; + var headers: std.ArrayList(std.http.Header) = .empty; + defer headers.deinit(allocator); + + try headers.append(allocator, .{ + .name = "Content-Type", + .value = context.response.headers.content_type + }); + + if (context.response.headers.fingerprint) |auth_token| { + var value = std.Io.Writer.Allocating.init(arena.allocator()); + + try value.writer.print("fingerprint={s}; Secure; Path=/", .{auth_token}); + + try headers.append(allocator, .{ + .name = "Set-Cookie", + .value = value.written(), + }); + } + try request.respond(response, .{ - .extra_headers = &.{ - .{ - .name = "Content-Type", - .value = context.response.headers.content_type - }, - }, + .extra_headers = headers.items }); } -pub fn from_type(T: type) @This() { - const info = @typeInfo(@TypeOf(T.handler)); +fn HandlerWrapper(T: type, name: []const u8) type { + const info = @typeInfo(@TypeOf(@field(T, name))); const return_type = info.@"fn".return_type orelse void; const payload_type = @typeInfo(return_type).error_union.payload; - const Handler = type: { - break :type struct { - pub fn call(ctx: *Context) anyerror![]const u8 { - const args = args: { - const tuple = std.meta.fields(std.meta.ArgsTuple(@TypeOf(T.handler))); + return struct { + pub fn call(ctx: *Context) anyerror![]const u8 { + const args = args: { + const tuple = std.meta.fields(std.meta.ArgsTuple(@TypeOf(@field(T, name)))); - if (tuple.len == 1) { - break :args .{ ctx }; - } else if (tuple.len == 2) { - const Body = tuple[1].@"type"; + if (tuple.len == 1) { + break :args .{ ctx }; + } else if (tuple.len == 2) { + const Body = tuple[1].@"type"; - var writer = std.Io.Writer.Allocating.init(ctx.allocator); - const interface = &writer.writer; + var writer = std.Io.Writer.Allocating.init(ctx.allocator); + const interface = &writer.writer; - var buffer: [1024]u8 = undefined; - const reader = try ctx.request.readerExpectContinue(&buffer); + var buffer: [1024]u8 = undefined; + const reader = try ctx.request.readerExpectContinue(&buffer); - try reader.streamExact64(interface, ctx.request.head.content_length orelse 0); + try reader.streamExact64(interface, ctx.request.head.content_length orelse 0); - const body = std.json.parseFromSliceLeaky( - Body, - ctx.allocator, - writer.written(), - .{} - ) catch return error.BadRequest; - break :args .{ ctx, body }; - } else { - @compileError("invalid amount of arguments for request function"); - } - }; - - if (payload_type == []const u8) { - return @call(.auto, T.handler, args); - } else if (payload_type == void) { - try @call(.auto, T.handler, args); - return ""; - } else { - var writer = std.Io.Writer.Allocating.init(ctx.allocator); - var stringify = std.json.Stringify { .writer = &writer.writer }; - try stringify.write(try @call(.auto, T.handler, args)); - return writer.written(); - } + const body = std.json.parseFromSliceLeaky( + Body, + ctx.allocator, + writer.written(), + .{} + ) catch return error.BadRequest; + break :args .{ ctx, body }; + } else { + @compileError("invalid amount of arguments for request function"); } }; + + if (payload_type == []const u8) { + return @call(.auto, @field(T, name), args); + } else if (payload_type == void) { + try @call(.auto, @field(T, name), args); + return ""; + } else { + var writer = std.Io.Writer.Allocating.init(ctx.allocator); + var stringify = std.json.Stringify { .writer = &writer.writer }; + try stringify.write(try @call(.auto, @field(T, name), args)); + return writer.written(); + } + } }; +} - return @This() { - .handler = Handler.call, - .needs_auth = T.needs_auth, - .method = T.method, +pub fn from_type(T: type) @This() { + return Self { + .get = if (@hasDecl(T, "get")) HandlerWrapper(T, "get").call else null, + .head = if (@hasDecl(T, "head")) HandlerWrapper(T, "head").call else null, + .post = if (@hasDecl(T, "post")) HandlerWrapper(T, "post").call else null, + .put = if (@hasDecl(T, "put")) HandlerWrapper(T, "put").call else null, + .delete = if (@hasDecl(T, "delete")) HandlerWrapper(T, "delete").call else null, + .connect = if (@hasDecl(T, "connect")) HandlerWrapper(T, "connect").call else null, + .options = if (@hasDecl(T, "options")) HandlerWrapper(T, "options").call else null, + .trace = if (@hasDecl(T, "trace")) HandlerWrapper(T, "trace").call else null, + .patch = if (@hasDecl(T, "patch")) HandlerWrapper(T, "patch").call else null, + .access = T.access, }; } |