Skip to content

Commit 2672aaa

Browse files
committed
refactor(app): use middleware to perform authorization
1 parent 262c5f0 commit 2672aaa

1 file changed

Lines changed: 76 additions & 79 deletions

File tree

src/App.zig

Lines changed: 76 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,94 @@ pub const Context = struct {
2121
trusted_proxies: []const network.Network,
2222
};
2323

24+
const Authorization = struct {
25+
pub const Config = struct {
26+
credential: []const u8,
27+
};
28+
29+
config: Config,
30+
allocator: std.mem.Allocator,
31+
32+
pub fn init(config: Config, mw_config: httpz.MiddlewareConfig) !Authorization {
33+
return .{ .config = config, .allocator = mw_config.allocator };
34+
}
35+
36+
pub fn execute(self: *const Authorization, req: *httpz.Request, res: *httpz.Response, executor: anytype) !void {
37+
const authorized = authorized: {
38+
const scheme = "Basic ";
39+
const optional_authorization = req.header("authorization");
40+
41+
if (optional_authorization) |authorization| {
42+
if (std.mem.startsWith(u8, authorization, scheme)) {
43+
const encoded = authorization[scheme.len..];
44+
45+
const decoder = std.base64.url_safe.Decoder;
46+
47+
const bufsize = decoder.calcSizeForSlice(encoded) catch break :authorized false;
48+
const got = try self.allocator.alloc(u8, bufsize);
49+
defer self.allocator.free(got);
50+
51+
decoder.decode(got, encoded) catch break :authorized false;
52+
53+
if (!std.mem.eql(u8, got, self.config.credential)) break :authorized false;
54+
55+
break :authorized true;
56+
}
57+
}
58+
59+
break :authorized false;
60+
};
61+
62+
if (!authorized) {
63+
respondError(res, .unauthorized);
64+
} else {
65+
return executor.next();
66+
}
67+
}
68+
};
69+
2470
server: httpz.Server(*Context),
71+
_api_middlewares: []const httpz.Middleware(*Context),
2572

2673
const App = @This();
2774

2875
pub fn init(ctx: *Context, config: httpz.Config) !App {
29-
var app = App{ .server = undefined };
76+
var app = App{
77+
.server = undefined,
78+
._api_middlewares = undefined,
79+
};
80+
3081
app.server = try httpz.Server(*Context).init(ctx.allocator, config, ctx);
3182

3283
var router = try app.server.router(.{});
33-
router.get("/api/bins", fetchBins, .{});
34-
router.put("/api/bins", createOrUpdateBin, .{});
35-
router.get("/api/bins/:bin", inspectBin, .{});
36-
router.delete("/api/bins/:bin", deleteBin, .{});
37-
router.get("/api/bins/:bin/captures", viewBin, .{});
38-
router.delete("/api/bins/:bin/captures", clearBin, .{});
39-
router.get("/api/bins/:bin/captures/:capture", inspectCapture, .{});
40-
router.delete("/api/bins/:bin/captures/:capture", deleteCapture, .{});
4184

4285
router.all("/access/:bin", captureAccess, .{});
43-
4486
router.get("/", serveDashboard, .{});
4587

88+
app._api_middlewares = middlewares: {
89+
if (ctx.auth) |credential| {
90+
break :middlewares try ctx.allocator.dupe(httpz.Middleware(*Context), &.{
91+
try app.server.middleware(Authorization, .{ .credential = credential }),
92+
});
93+
} else {
94+
break :middlewares &.{};
95+
}
96+
};
97+
98+
var api_router = router.group(
99+
"/api",
100+
.{ .middlewares = app._api_middlewares },
101+
);
102+
103+
api_router.get("/bins", fetchBins, .{});
104+
api_router.put("/bins", createOrUpdateBin, .{});
105+
api_router.get("/bins/:bin", inspectBin, .{});
106+
api_router.delete("/bins/:bin", deleteBin, .{});
107+
api_router.get("/bins/:bin/captures", viewBin, .{});
108+
api_router.delete("/bins/:bin/captures", clearBin, .{});
109+
api_router.get("/bins/:bin/captures/:capture", inspectCapture, .{});
110+
api_router.delete("/bins/:bin/captures/:capture", deleteCapture, .{});
111+
46112
return app;
47113
}
48114

@@ -142,35 +208,6 @@ fn captureAccess(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void
142208
}
143209
}
144210

145-
fn authorize(ctx: *Context, req: *httpz.Request) !bool {
146-
if (ctx.auth) |auth| {
147-
const scheme = "Basic ";
148-
const optional_authorization = req.header("authorization");
149-
150-
if (optional_authorization) |authorization| {
151-
if (std.mem.startsWith(u8, authorization, scheme)) {
152-
const enc_cred = authorization[scheme.len..];
153-
154-
const decoder = std.base64.url_safe.Decoder;
155-
156-
const bufsize = decoder.calcSizeForSlice(enc_cred) catch return false;
157-
const cred = try ctx.allocator.alloc(u8, bufsize);
158-
defer ctx.allocator.free(cred);
159-
160-
decoder.decode(cred, enc_cred) catch return false;
161-
162-
if (!std.mem.eql(u8, cred, auth)) return false;
163-
164-
return true;
165-
}
166-
}
167-
168-
return false;
169-
} else {
170-
return true;
171-
}
172-
}
173-
174211
fn isValidBinName(name: []const u8) bool {
175212
if (name.len == 0) return false;
176213

@@ -185,11 +222,6 @@ fn isValidBinName(name: []const u8) bool {
185222
}
186223

187224
fn viewBin(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
188-
if (!try authorize(ctx, req)) {
189-
respondError(res, .unauthorized);
190-
return;
191-
}
192-
193225
const bin_name = req.param("bin").?;
194226

195227
const bin = try sql_query.bins.getId(ctx.db, bin_name) orelse {
@@ -220,11 +252,6 @@ fn viewBin(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
220252
}
221253

222254
fn fetchBins(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
223-
if (!try authorize(ctx, req)) {
224-
respondError(res, .unauthorized);
225-
return;
226-
}
227-
228255
const query = try req.query();
229256
const options = models.PageParams.parseFromStringKeyValue(query) catch {
230257
respondError(res, .unprocessable_entity);
@@ -242,11 +269,6 @@ fn fetchBins(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
242269
}
243270

244271
fn createOrUpdateBin(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
245-
if (!try authorize(ctx, req)) {
246-
respondError(res, .unauthorized);
247-
return;
248-
}
249-
250272
var bin = req.json(models.Bin) catch null orelse {
251273
respondError(res, .bad_request);
252274
return;
@@ -274,11 +296,6 @@ fn createOrUpdateBin(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !
274296
}
275297

276298
fn inspectBin(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
277-
if (!try authorize(ctx, req)) {
278-
respondError(res, .unauthorized);
279-
return;
280-
}
281-
282299
const bin_name = req.param("bin").?;
283300

284301
var arena = std.heap.ArenaAllocator.init(ctx.allocator);
@@ -293,11 +310,6 @@ fn inspectBin(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
293310
}
294311

295312
fn deleteBin(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
296-
if (!try authorize(ctx, req)) {
297-
respondError(res, .unauthorized);
298-
return;
299-
}
300-
301313
const bin_name = req.param("bin").?;
302314

303315
try sql_query.bins.delete(ctx.db, bin_name);
@@ -306,11 +318,6 @@ fn deleteBin(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
306318
}
307319

308320
fn clearBin(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
309-
if (!try authorize(ctx, req)) {
310-
respondError(res, .unauthorized);
311-
return;
312-
}
313-
314321
const bin_name = req.param("bin").?;
315322
const bin = try sql_query.bins.getId(ctx.db, bin_name) orelse {
316323
respondError(res, .not_found);
@@ -323,11 +330,6 @@ fn clearBin(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
323330
}
324331

325332
fn inspectCapture(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
326-
if (!try authorize(ctx, req)) {
327-
respondError(res, .unauthorized);
328-
return;
329-
}
330-
331333
const bin_name = req.param("bin").?;
332334
const capture_id = std.fmt.parseInt(i64, req.param("capture").?, 10) catch {
333335
respondError(res, .unprocessable_entity);
@@ -351,11 +353,6 @@ fn inspectCapture(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !voi
351353
}
352354

353355
fn deleteCapture(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
354-
if (!try authorize(ctx, req)) {
355-
respondError(res, .unauthorized);
356-
return;
357-
}
358-
359356
const bin_name = req.param("bin").?;
360357
const capture = std.fmt.parseInt(i64, req.param("capture").?, 10) catch {
361358
respondError(res, .unprocessable_entity);

0 commit comments

Comments
 (0)