aboutsummaryrefslogtreecommitdiff
path: root/src/storage/user.zig
blob: c981661324404cdb107479e5de8a8b6e18ef8160 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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;
}