diff --git a/build.zig b/build.zig index c83f5e9..7fdea99 100644 --- a/build.zig +++ b/build.zig @@ -8,6 +8,8 @@ pub fn build(b: *std.Build) void { .root_source_file = .{ .path = "assets.zig" }, }); + const uuid_mod = b.dependency("uuid", .{}).module("uuid"); + const exe = b.addExecutable(.{ .name = "anvilauth", .root_source_file = .{ .path = "src/main.zig" }, @@ -17,6 +19,7 @@ pub fn build(b: *std.Build) void { }); exe.root_module.addImport("assets", assets_mod); + exe.root_module.addImport("uuid", uuid_mod); exe.linkSystemLibrary("libpq"); exe.linkSystemLibrary("openssl"); diff --git a/build.zig.zon b/build.zig.zon index 0e4e521..cffb7de 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,6 +2,11 @@ .name = "anvilauth", .version = "0.0.0", - .dependencies = .{}, + .dependencies = .{ + .uuid = .{ + .url = "git+https://github.com/silversquirl/zig-uuid.git#66b79e3a32c255c7450fac7764847fd6453e94c5", + .hash = "1220c4d64594998417a77f84bc6beba3f8aa7e67c0c69fe060c2a7f6385b4d56a34e", + }, + }, .paths = .{""}, } diff --git a/src/Db.zig b/src/Db.zig index 43bf097..157f0a0 100644 --- a/src/Db.zig +++ b/src/Db.zig @@ -1,10 +1,10 @@ const std = @import("std"); +const UUID = @import("uuid").Uuid; + const ffi = @import("ffi.zig"); const c = ffi.c; -const Id = @import("Id.zig"); - con: *c.PGconn, const Db = @This(); @@ -67,7 +67,7 @@ pub fn execParams(self: Db, query: [:0]const u8, params: anytype) Result { len.* = @intCast(bytes.len); format.* = 1; }, - Id => { + UUID => { val.* = ¶m.bytes; len.* = param.bytes.len; format.* = 1; @@ -133,7 +133,7 @@ pub const Result = struct { pub inline fn get(self: Result, comptime T: type, row: c_int, col: c_int) T { return switch (T) { []const u8 => c.PQgetvalue(self.res, row, col)[0..@intCast(c.PQgetlength(self.res, row, col))], - Id => .{ .bytes = c.PQgetvalue(self.res, row, col)[0..16].* }, + UUID => .{ .bytes = c.PQgetvalue(self.res, row, col)[0..16].* }, else => @compileError("unsuppored type: " ++ @typeName(T)), }; } diff --git a/src/Id.zig b/src/Id.zig deleted file mode 100644 index 7692fb7..0000000 --- a/src/Id.zig +++ /dev/null @@ -1,30 +0,0 @@ -/// This file implements something similar to UUIDs without the bullshit. -/// It implements a simple even U-er UID, which is simply 16 random bytes. -const std = @import("std"); - -bytes: [16]u8, - -const Id = @This(); - -/// Returns a hex encoded version of the ID. -pub fn toString(self: Id) [32]u8 { - var ret: [32]u8 = undefined; - _ = std.fmt.bufPrint(&ret, "{}", .{std.fmt.fmtSliceHexLower(&self.bytes)}) catch unreachable; - return ret; -} - -pub fn genRandom(rand: std.rand.Random) Id { - var bytes: [16]u8 = undefined; - rand.bytes(&bytes); - return .{ .bytes = bytes }; -} - -// Parses the given string as an ID, or returns null if it is invalid. -pub fn parse(str: []const u8) ?Id { - if (str.len != 32) return null; - var bytes: [16]u8 = undefined; - for (&bytes, 0..) |*out, ihalf| { - out.* = std.fmt.parseInt(u8, str[ihalf * 2 ..][0..2], 16) catch return null; - } - return .{ .bytes = bytes }; -} diff --git a/src/conutil.zig b/src/conutil.zig index c702662..e4c409e 100644 --- a/src/conutil.zig +++ b/src/conutil.zig @@ -89,4 +89,3 @@ pub fn parseQueryParametersOrDefaults(params: []const u8, comptime T: type) Quer return out; } - diff --git a/src/json_user_writer.zig b/src/json_user_writer.zig index 64b1129..f769a08 100644 --- a/src/json_user_writer.zig +++ b/src/json_user_writer.zig @@ -1,9 +1,9 @@ const std = @import("std"); const c = ffi.c; -const ffi = @import("ffi.zig"); +const UUID = @import("uuid").Uuid; -const Id = @import("Id.zig"); +const ffi = @import("ffi.zig"); pub fn jsonUserWriter( alloc: std.mem.Allocator, @@ -29,12 +29,12 @@ pub fn JsonUserWriter(Writer: type) type { const Self = @This(); - pub fn writeHeaderAndStartProperties(self: *Self, id: Id, name: []const u8) !void { + pub fn writeHeaderAndStartProperties(self: *Self, id: UUID, name: []const u8) !void { std.debug.assert(self.state == .start); try self.json_stream.beginObject(); try self.json_stream.objectField("id"); - try self.json_stream.write(&id.toString()); + try self.json_stream.write(&id.toStringCompact()); try self.json_stream.objectField("name"); try self.json_stream.write(name); try self.json_stream.objectField("properties"); @@ -88,7 +88,7 @@ pub fn JsonUserWriter(Writer: type) type { pub fn texturesProperty( self: *Self, - user_id: Id, + user_id: UUID, user_name: []const u8, skin_url: []const u8, ) !void { @@ -100,7 +100,7 @@ pub fn JsonUserWriter(Writer: type) type { try write_stream.objectField("timestamp"); try write_stream.write(std.time.timestamp()); try write_stream.objectField("profileId"); - try write_stream.write(&user_id.toString()); + try write_stream.write(&user_id.toStringCompact()); try write_stream.objectField("profileName"); try write_stream.write(user_name); try write_stream.objectField("textures"); diff --git a/src/routes/aliapi/api/profiles/minecraft.zig b/src/routes/aliapi/api/profiles/minecraft.zig index bc3b9aa..79df0ce 100644 --- a/src/routes/aliapi/api/profiles/minecraft.zig +++ b/src/routes/aliapi/api/profiles/minecraft.zig @@ -1,8 +1,9 @@ const std = @import("std"); +const UUID = @import("uuid").Uuid; + const conutil = @import("../../../../conutil.zig"); -const Id = @import("../../../../Id.zig"); const State = @import("../../../../State.zig"); pub fn matches(path: []const u8) bool { @@ -72,12 +73,12 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { try json_writer.beginArray(); for (0..@intCast(db_res.rows())) |rowidx| { - const id = db_res.get(Id, @intCast(rowidx), 0); + const id = db_res.get(UUID, @intCast(rowidx), 0); const name = db_res.get([]const u8, @intCast(rowidx), 1); try json_writer.beginObject(); try json_writer.objectField("id"); - try json_writer.write(&id.toString()); + try json_writer.write(&id.toStringCompact()); try json_writer.objectField("name"); try json_writer.write(name); try json_writer.endObject(); diff --git a/src/routes/aliapi/authserver/authenticate.zig b/src/routes/aliapi/authserver/authenticate.zig index 4fc4c04..9bca6db 100644 --- a/src/routes/aliapi/authserver/authenticate.zig +++ b/src/routes/aliapi/authserver/authenticate.zig @@ -1,10 +1,11 @@ const std = @import("std"); const c = ffi.c; +const UUID = @import("uuid").Uuid; + const ffi = @import("../../../ffi.zig"); const conutil = @import("../../../conutil.zig"); -const Id = @import("../../../Id.zig"); const State = @import("../../../State.zig"); pub fn matches(path: []const u8) bool { @@ -94,7 +95,7 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { if (sel_result.rows() != 1 or sel_result.cols() != 1) return error.InvalidResultFromPostgresServer; - const userid = sel_result.get(Id, 0, 0); + const userid = sel_result.get(UUID, 0, 0); const Profile = struct { name: []const u8, @@ -120,14 +121,19 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { const client_token: [:0]const u8 = req_payload.value.clientToken orelse gentoken: { // TODO: according to https://wiki.vg/Legacy_Mojang_Authentication, the normal server // would invalidate all existing tokens here. This makes no sense, so we don't do it. - @memcpy(&gen_token_buf, &Id.genRandom(state.rand).toString()); + var rand_bytes: [16]u8 = undefined; + state.rand.bytes(&rand_bytes); + @memcpy(&gen_token_buf, &UUID.fromRawBytes(4, rand_bytes) + .toStringCompact()); break :gentoken &gen_token_buf; }; // remains valid for one week const expiry = std.time.timestamp() + std.time.ms_per_week; - const tokenid = Id.genRandom(state.rand); + var tokenid_bytes: [16]u8 = undefined; + state.rand.bytes(&tokenid_bytes); + const tokenid = UUID.fromRawBytes(4, tokenid_bytes); const add_tok_stat = state.db.execParams( \\INSERT INTO tokens (id, userid, expiry, client_token) @@ -138,7 +144,7 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { defer add_tok_stat.deinit(); try add_tok_stat.expectCommand(); - const uid_hex = userid.toString(); + const uid_hex = userid.toStringCompact(); const profile = Profile{ .name = req_payload.value.username, @@ -158,7 +164,7 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { }, } else null, .clientToken = client_token, - .accessToken = &tokenid.toString(), + .accessToken = &tokenid.toStringCompact(), .availableProfiles = &.{profile}, .selectedProfile = profile, }; diff --git a/src/routes/aliapi/index.zig b/src/routes/aliapi/index.zig index 6c96342..f15de9d 100644 --- a/src/routes/aliapi/index.zig +++ b/src/routes/aliapi/index.zig @@ -36,10 +36,8 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { const json = try std.json.stringifyAlloc(state.allocator, response_payload, .{}); defer state.allocator.free(json); - try req.respond(json, .{ - .extra_headers = &.{.{ - .name = "Content-Type", - .value = "application/json", - }} - }); + try req.respond(json, .{ .extra_headers = &.{.{ + .name = "Content-Type", + .value = "application/json", + }} }); } diff --git a/src/routes/aliapi/sessionserver/session/minecraft/has_joined.zig b/src/routes/aliapi/sessionserver/session/minecraft/has_joined.zig index 8d60052..89ec780 100644 --- a/src/routes/aliapi/sessionserver/session/minecraft/has_joined.zig +++ b/src/routes/aliapi/sessionserver/session/minecraft/has_joined.zig @@ -1,10 +1,11 @@ const std = @import("std"); const c = ffi.c; +const UUID = @import("uuid").Uuid; + const conutil = @import("../../../../../conutil.zig"); const ffi = @import("../../../../../ffi.zig"); -const Id = @import("../../../../../Id.zig"); const jsonUserWriter = @import("../../../../../json_user_writer.zig").jsonUserWriter; const State = @import("../../../../../State.zig"); @@ -37,7 +38,7 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { if (sel_dbret.cols() != 1) return error.InvalidResultFromPostgresServer; if (sel_dbret.rows() >= 1) { - const id = sel_dbret.get(Id, 0, 0); + const id = sel_dbret.get(UUID, 0, 0); const skin_url = try state.getSkinUrl(params.username); defer if (skin_url) |url| state.allocator.free(url); diff --git a/src/routes/aliapi/sessionserver/session/minecraft/join.zig b/src/routes/aliapi/sessionserver/session/minecraft/join.zig index c329e55..d64c3e3 100644 --- a/src/routes/aliapi/sessionserver/session/minecraft/join.zig +++ b/src/routes/aliapi/sessionserver/session/minecraft/join.zig @@ -1,10 +1,11 @@ const std = @import("std"); const c = ffi.c; +const UUID = @import("uuid").Uuid; + const conutil = @import("../../../../../conutil.zig"); const ffi = @import("../../../../../ffi.zig"); -const Id = @import("../../../../../Id.zig"); const State = @import("../../../../../State.zig"); pub fn matches(path: []const u8) bool { @@ -28,13 +29,13 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { }; defer req_payload.deinit(); - const access_token = Id.parse(req_payload.value.accessToken) orelse { - try conutil.sendJsonError(req, .bad_request, "accessToken is not a valid ID!", .{}); + const access_token = UUID.fromString(req_payload.value.accessToken) catch { + try conutil.sendJsonError(req, .bad_request, "accessToken is not a valid UUID!", .{}); return; }; - const sel_profile = Id.parse(req_payload.value.selectedProfile) orelse { - try conutil.sendJsonError(req, .bad_request, "selectedProfile is not a valid ID!", .{}); + const sel_profile = UUID.fromString(req_payload.value.selectedProfile) catch { + try conutil.sendJsonError(req, .bad_request, "selectedProfile is not a valid UUID!", .{}); return; }; @@ -45,7 +46,7 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { if (dbret.cols() != 1) return error.InvalidResultFromPostgresServer; if (dbret.rows() >= 1) { - const token_user = dbret.get(Id, 0, 0); + const token_user = dbret.get(UUID, 0, 0); if (std.mem.eql(u8, &sel_profile.bytes, &token_user.bytes)) { const ins_dbret = state.db.execParams( \\INSERT INTO joins (userid, serverid) diff --git a/src/routes/aliapi/sessionserver/session/minecraft/profile.zig b/src/routes/aliapi/sessionserver/session/minecraft/profile.zig index 35aee47..da259c4 100644 --- a/src/routes/aliapi/sessionserver/session/minecraft/profile.zig +++ b/src/routes/aliapi/sessionserver/session/minecraft/profile.zig @@ -1,10 +1,11 @@ const std = @import("std"); const c = ffi.c; +const UUID = @import("uuid").Uuid; + const ffi = @import("../../../../../ffi.zig"); const conutil = @import("../../../../../conutil.zig"); -const Id = @import("../../../../../Id.zig"); const jsonUserWriter = @import("../../../../../json_user_writer.zig").jsonUserWriter; const State = @import("../../../../../State.zig"); @@ -18,11 +19,11 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { const req_url = try std.Uri.parseWithoutScheme(req.head.target); // This is sound as we only go here if the path starts with path_prefix. - const profile_id = Id.parse(req_url.path[path_prefix.len..]) orelse { + const profile_id = UUID.fromString(req_url.path[path_prefix.len..]) catch { try conutil.sendJsonError( req, .bad_request, - "not a valid UUID: {s} (NOTE: AnvilAuth technically doesn't use UUIDs, this endpoint expects 16 hex-encoded, undelimited bytes.)", + "not a valid UUID: {s}", .{req_url.path[path_prefix.len..]}, ); return;