Add environments, use pretty-printed JSON in development

Fix secret generation - overallocate length to ensure we have enough
bytes.

Error if no secret provided in production mode.
This commit is contained in:
Bob Farrell 2024-03-28 22:37:59 +00:00
parent 95a8330629
commit ec045cccd6
5 changed files with 35 additions and 17 deletions

View File

@ -7,8 +7,8 @@
.hash = "1220bfc5c29bc930b5a524c210712ef65c6cde6770450899bef01164a3089e6707fa", .hash = "1220bfc5c29bc930b5a524c210712ef65c6cde6770450899bef01164a3089e6707fa",
}, },
.zmpl = .{ .zmpl = .{
.url = "https://github.com/jetzig-framework/zmpl/archive/9e2df115c9f17e92fb60a4d09bf55ea48d0388b0.tar.gz", .url = "https://github.com/jetzig-framework/zmpl/archive/ffdbd3767da28d5ab07c1a786ea778152d2f79f6.tar.gz",
.hash = "1220820b7f5f3e01b7dc976d32cf9ff65d44dee2642533f4b8104e19a824e802d7e1", .hash = "12202cf05fd4ba2482a9b4b89c632b435310a76ac501b7a3d87dfd41006748dd138d",
}, },
.args = .{ .args = .{
.url = "https://github.com/MasterQ32/zig-args/archive/01d72b9a0128c474aeeb9019edd48605fa6d95f7.tar.gz", .url = "https://github.com/MasterQ32/zig-args/archive/01d72b9a0128c474aeeb9019edd48605fa6d95f7.tar.gz",

View File

@ -6,9 +6,9 @@ pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8) !v
_ = args; _ = args;
_ = cwd; _ = cwd;
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var secret: [44]u8 = undefined; var secret: [128]u8 = undefined;
for (0..44) |index| { for (0..128) |index| {
secret[index] = chars[std.crypto.random.intRangeAtMost(u8, 0, chars.len)]; secret[index] = chars[std.crypto.random.intRangeAtMost(u8, 0, chars.len)];
} }

View File

@ -271,7 +271,7 @@ fn generateRoutesForView(self: *Self, views_dir: std.fs.Dir, path: []const u8) !
var json_buf = std.ArrayList(u8).init(self.allocator); var json_buf = std.ArrayList(u8).init(self.allocator);
defer json_buf.deinit(); defer json_buf.deinit();
const json_writer = json_buf.writer(); const json_writer = json_buf.writer();
try item.toJson(json_writer); try item.toJson(json_writer, false, 0);
var encoded_buf = std.ArrayList(u8).init(self.allocator); var encoded_buf = std.ArrayList(u8).init(self.allocator);
defer encoded_buf.deinit(); defer encoded_buf.deinit();
const writer = encoded_buf.writer(); const writer = encoded_buf.writer();

View File

@ -8,12 +8,13 @@ const Environment = @This();
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
pub const EnvironmentName = enum { development, production };
const Options = struct { const Options = struct {
help: bool = false, help: bool = false,
bind: []const u8 = "127.0.0.1", bind: []const u8 = "127.0.0.1",
port: u16 = 8080, port: u16 = 8080,
// TODO: environment: EnvironmentName = .development,
// environment: []const u8 = "development",
log: []const u8 = "-", log: []const u8 = "-",
@"log-error": []const u8 = "-", @"log-error": []const u8 = "-",
@"log-level": jetzig.loggers.LogLevel = .INFO, @"log-level": jetzig.loggers.LogLevel = .INFO,
@ -24,8 +25,7 @@ const Options = struct {
.h = "help", .h = "help",
.b = "bind", .b = "bind",
.p = "port", .p = "port",
// TODO: .e = "environment",
// .e = "environment",
.d = "detach", .d = "detach",
}; };
@ -35,8 +35,7 @@ const Options = struct {
.option_docs = .{ .option_docs = .{
.bind = "IP address/hostname to bind to (default: 127.0.0.1)", .bind = "IP address/hostname to bind to (default: 127.0.0.1)",
.port = "Port to listen on (default: 8080)", .port = "Port to listen on (default: 8080)",
// TODO: .environment = "Set the server environment. Must be one of: { development, production } (default: development)",
// .environment = "Load an environment configuration from src/app/environments/<environment>.zig",
.log = "Path to log file. Use '-' for stdout (default: '-')", .log = "Path to log file. Use '-' for stdout (default: '-')",
.@"log-error" = .@"log-error" =
\\Optional path to separate error log file. Use '-' for stderr. If omitted, errors are logged to the location specified by the `log` option (or stderr if `log` is '-'). \\Optional path to separate error log file. Use '-' for stderr. If omitted, errors are logged to the location specified by the `log` option (or stderr if `log` is '-').
@ -94,9 +93,11 @@ pub fn getServerOptions(self: Environment) !jetzig.http.Server.ServerOptions {
std.process.exit(1); std.process.exit(1);
} }
const environment = options.options.environment;
// TODO: Generate nonce per session - do research to confirm correct best practice. // TODO: Generate nonce per session - do research to confirm correct best practice.
const secret_len = jetzig.http.Session.Cipher.key_length + jetzig.http.Session.Cipher.nonce_length; const secret_len = jetzig.http.Session.Cipher.key_length + jetzig.http.Session.Cipher.nonce_length;
const secret = try self.getSecret(&logger, secret_len); const secret = (try self.getSecret(&logger, secret_len, environment))[0..secret_len];
if (secret.len != secret_len) { if (secret.len != secret_len) {
try logger.ERROR("Expected secret length: {}, found: {}.", .{ secret_len, secret.len }); try logger.ERROR("Expected secret length: {}, found: {}.", .{ secret_len, secret.len });
@ -110,6 +111,7 @@ pub fn getServerOptions(self: Environment) !jetzig.http.Server.ServerOptions {
.bind = try self.allocator.dupe(u8, options.options.bind), .bind = try self.allocator.dupe(u8, options.options.bind),
.port = options.options.port, .port = options.options.port,
.detach = options.options.detach, .detach = options.options.detach,
.environment = environment,
}; };
} }
@ -132,11 +134,18 @@ fn getLogFile(stream: enum { stdout, stderr }, options: Options) !std.fs.File {
return file; return file;
} }
fn getSecret(self: Environment, logger: *jetzig.loggers.Logger, comptime len: u10) ![]const u8 { fn getSecret(self: Environment, logger: *jetzig.loggers.Logger, comptime len: u10, environment: EnvironmentName) ![]const u8 {
return std.process.getEnvVarOwned(self.allocator, "JETZIG_SECRET") catch |err| { const env_var = "JETZIG_SECRET";
return std.process.getEnvVarOwned(self.allocator, env_var) catch |err| {
switch (err) { switch (err) {
error.EnvironmentVariableNotFound => { error.EnvironmentVariableNotFound => {
// TODO: Make this a failure when running in non-development mode. if (environment != .development) {
try logger.ERROR("Environment variable `{s}` must be defined in production mode.", .{env_var});
try logger.ERROR("Run `jetzig generate secret` to generate an appropriate value.", .{});
std.process.exit(1);
}
const secret = try jetzig.util.generateSecret(self.allocator, len); const secret = try jetzig.util.generateSecret(self.allocator, len);
try logger.WARN( try logger.WARN(
"Running in development mode, using auto-generated cookie encryption key: {s}", "Running in development mode, using auto-generated cookie encryption key: {s}",

View File

@ -9,6 +9,7 @@ pub const ServerOptions = struct {
port: u16, port: u16,
secret: []const u8, secret: []const u8,
detach: bool, detach: bool,
environment: jetzig.Environment.EnvironmentName,
}; };
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
@ -48,7 +49,11 @@ pub fn listen(self: *Self) !void {
self.initialized = true; self.initialized = true;
try self.logger.INFO("Listening on http://{s}:{}", .{ self.options.bind, self.options.port }); try self.logger.INFO("Listening on http://{s}:{} [{s}]", .{
self.options.bind,
self.options.port,
@tagName(self.options.environment),
});
try self.processRequests(); try self.processRequests();
} }
@ -167,7 +172,11 @@ fn renderJSON(
if (data.value) |_| {} else _ = try data.object(); if (data.value) |_| {} else _ = try data.object();
rendered.content = try data.toJson(); rendered.content = if (self.options.environment == .development)
try data.toPrettyJson()
else
try data.toJson();
request.setResponse(rendered, .{}); request.setResponse(rendered, .{});
} else { } else {
request.setResponse(try renderNotFound(request), .{}); request.setResponse(try renderNotFound(request), .{});