aboutsummaryrefslogtreecommitdiff
path: root/src/routes/handler-info.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes/handler-info.zig')
-rw-r--r--src/routes/handler-info.zig201
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,
};
}