diff options
Diffstat (limited to 'src/storage')
| -rw-r--r-- | src/storage/root.zig | 57 | ||||
| -rw-r--r-- | src/storage/user.zig | 133 |
2 files changed, 190 insertions, 0 deletions
diff --git a/src/storage/root.zig b/src/storage/root.zig new file mode 100644 index 0000000..bf9803e --- /dev/null +++ b/src/storage/root.zig @@ -0,0 +1,57 @@ +const std = @import("std"); +const config = @import("../config.zig"); +const prompt = @import("../prompt.zig"); + +pub const User = @import("user.zig"); + +const Self = @This(); + +dir: std.fs.Dir, + +pub fn init(allocator: std.mem.Allocator) !Self { + const dir = std.fs.cwd().openDir(config.storage_path, .{}) catch blk: { + try std.fs.cwd().makeDir(config.storage_path); + break :blk try std.fs.cwd().openDir(config.storage_path, .{}); + }; + + var self = Self { .dir = dir }; + + dir.access("user", .{}) catch |err| switch (err) { + error.FileNotFound => { + const name = try prompt.read("Username", allocator); + defer allocator.free(name); + + const full_name = try prompt.read("Full Name", allocator); + defer allocator.free(full_name); + + const birthday = try prompt.read("Birthday", allocator); + defer allocator.free(birthday); + + const password = try prompt.read("Password", allocator); + defer allocator.free(password); + + try self.dir.makeDir("user"); + + var user: User = try .new( + &self, + name, + full_name, + birthday, + password, + true, + allocator, + ); + defer user.deinit(); + + try user.sync(); + }, + else => return err, + }; + + return self; +} + +pub fn deinit(self: *Self) void { + self.dir.close(); + self.* = undefined; +} diff --git a/src/storage/user.zig b/src/storage/user.zig new file mode 100644 index 0000000..c981661 --- /dev/null +++ b/src/storage/user.zig @@ -0,0 +1,133 @@ +const std = @import("std"); +const Storage = @import("root.zig"); + +const Self = @This(); + +pub const UserInfo = struct { + name: []const u8, + full_name: []const u8, + birthday: []const u8, + hash: []const u8, + is_admin: bool, +}; + +dir: std.fs.Dir, +info: UserInfo, +arena: std.heap.ArenaAllocator, + +pub fn open( + storage: *Storage, + name: []const u8, + inner_allocator: std.mem.Allocator +) !Self { + var arena: std.heap.ArenaAllocator = .init(inner_allocator); + errdefer arena.deinit(); + const allocator = arena.allocator(); + + var user_dir = try storage.dir.openDir("user", .{}); + defer user_dir.close(); + + var dir = try user_dir.openDir(name, .{}); + errdefer dir.close(); + + const file = try dir.openFile("info.json", .{}); + defer file.close(); + const content = try file.readToEndAlloc(allocator, std.math.maxInt(usize)); + + const info = try std.json.parseFromSliceLeaky(UserInfo, allocator, content, .{}); + + return .{ + .dir = dir, + .arena = arena, + .info = info, + }; +} + +pub fn new( + storage: *Storage, + name: []const u8, + full_name: []const u8, + birthday: []const u8, + password: []const u8, + is_admin: bool, + inner_allocator: std.mem.Allocator +) !Self { + var arena: std.heap.ArenaAllocator = .init(inner_allocator); + const allocator = arena.allocator(); + errdefer arena.deinit(); + + var user_dir = try storage.dir.openDir("user", .{}); + defer user_dir.close(); + + try user_dir.makeDir(name); + var dir = try user_dir.openDir(name, .{}); + errdefer dir.close(); + + var file = try dir.createFile("info.json", .{}); + file.close(); + + const hash_buf = try allocator.alloc(u8, 256); + const hash = try std.crypto.pwhash.bcrypt.strHash( + password, + .{ + .params = .{ + .rounds_log = 3, + .silently_truncate_password = false, + }, + .encoding = .phc, + }, + hash_buf + ); + + const info: UserInfo = .{ + .name = name, + .full_name = full_name, + .birthday = birthday, + .hash = hash, + .is_admin = is_admin, + }; + + + return .{ + .dir = dir, + .arena = arena, + .info = info, + }; +} + +pub fn sync(self: *Self) !void { + const file = try self.dir.openFile("info.json", .{ .mode = .write_only }); + defer file.close(); + + var buffer: [1024]u8 = undefined; + var writer = file.writer(&buffer); + + var stringify = std.json.Stringify { + .writer = &writer.interface, + .options = .{ .whitespace = .indent_2 } + }; + + try stringify.write(self.info); + try writer.interface.flush(); +} + +pub fn check_password(self: *Self, password: []const u8) bool { + std.crypto.pwhash.bcrypt.strVerify(self.info.hash, password, .{ + .silently_truncate_password = false, + }) catch return false; + return true; +} + +pub fn deinit(self: *Self) void { + self.dir.close(); + self.arena.deinit(); + self.* = undefined; +} + +pub fn exists(storage: *Storage, name: []const u8) bool { + var user = storage.dir.openDir("user", .{}) catch return false; + defer user.deinit(); + + user.access(name) catch return false; + return true; +} |