feat: use UUID library

This commit is contained in:
LordMZTE 2024-03-06 14:39:23 +01:00
parent 0166219065
commit 94d96f1657
Signed by: LordMZTE
GPG key ID: B64802DC33A64FF6
12 changed files with 53 additions and 68 deletions

View file

@ -8,6 +8,8 @@ pub fn build(b: *std.Build) void {
.root_source_file = .{ .path = "assets.zig" }, .root_source_file = .{ .path = "assets.zig" },
}); });
const uuid_mod = b.dependency("uuid", .{}).module("uuid");
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "anvilauth", .name = "anvilauth",
.root_source_file = .{ .path = "src/main.zig" }, .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("assets", assets_mod);
exe.root_module.addImport("uuid", uuid_mod);
exe.linkSystemLibrary("libpq"); exe.linkSystemLibrary("libpq");
exe.linkSystemLibrary("openssl"); exe.linkSystemLibrary("openssl");

View file

@ -2,6 +2,11 @@
.name = "anvilauth", .name = "anvilauth",
.version = "0.0.0", .version = "0.0.0",
.dependencies = .{}, .dependencies = .{
.uuid = .{
.url = "git+https://github.com/silversquirl/zig-uuid.git#66b79e3a32c255c7450fac7764847fd6453e94c5",
.hash = "1220c4d64594998417a77f84bc6beba3f8aa7e67c0c69fe060c2a7f6385b4d56a34e",
},
},
.paths = .{""}, .paths = .{""},
} }

View file

@ -1,10 +1,10 @@
const std = @import("std"); const std = @import("std");
const UUID = @import("uuid").Uuid;
const ffi = @import("ffi.zig"); const ffi = @import("ffi.zig");
const c = ffi.c; const c = ffi.c;
const Id = @import("Id.zig");
con: *c.PGconn, con: *c.PGconn,
const Db = @This(); const Db = @This();
@ -67,7 +67,7 @@ pub fn execParams(self: Db, query: [:0]const u8, params: anytype) Result {
len.* = @intCast(bytes.len); len.* = @intCast(bytes.len);
format.* = 1; format.* = 1;
}, },
Id => { UUID => {
val.* = &param.bytes; val.* = &param.bytes;
len.* = param.bytes.len; len.* = param.bytes.len;
format.* = 1; 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 { pub inline fn get(self: Result, comptime T: type, row: c_int, col: c_int) T {
return switch (T) { return switch (T) {
[]const u8 => c.PQgetvalue(self.res, row, col)[0..@intCast(c.PQgetlength(self.res, row, col))], []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)), else => @compileError("unsuppored type: " ++ @typeName(T)),
}; };
} }

View file

@ -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 };
}

View file

@ -89,4 +89,3 @@ pub fn parseQueryParametersOrDefaults(params: []const u8, comptime T: type) Quer
return out; return out;
} }

View file

@ -1,9 +1,9 @@
const std = @import("std"); const std = @import("std");
const c = ffi.c; 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( pub fn jsonUserWriter(
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
@ -29,12 +29,12 @@ pub fn JsonUserWriter(Writer: type) type {
const Self = @This(); 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); std.debug.assert(self.state == .start);
try self.json_stream.beginObject(); try self.json_stream.beginObject();
try self.json_stream.objectField("id"); 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.objectField("name");
try self.json_stream.write(name); try self.json_stream.write(name);
try self.json_stream.objectField("properties"); try self.json_stream.objectField("properties");
@ -88,7 +88,7 @@ pub fn JsonUserWriter(Writer: type) type {
pub fn texturesProperty( pub fn texturesProperty(
self: *Self, self: *Self,
user_id: Id, user_id: UUID,
user_name: []const u8, user_name: []const u8,
skin_url: []const u8, skin_url: []const u8,
) !void { ) !void {
@ -100,7 +100,7 @@ pub fn JsonUserWriter(Writer: type) type {
try write_stream.objectField("timestamp"); try write_stream.objectField("timestamp");
try write_stream.write(std.time.timestamp()); try write_stream.write(std.time.timestamp());
try write_stream.objectField("profileId"); 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.objectField("profileName");
try write_stream.write(user_name); try write_stream.write(user_name);
try write_stream.objectField("textures"); try write_stream.objectField("textures");

View file

@ -1,8 +1,9 @@
const std = @import("std"); const std = @import("std");
const UUID = @import("uuid").Uuid;
const conutil = @import("../../../../conutil.zig"); const conutil = @import("../../../../conutil.zig");
const Id = @import("../../../../Id.zig");
const State = @import("../../../../State.zig"); const State = @import("../../../../State.zig");
pub fn matches(path: []const u8) bool { 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(); try json_writer.beginArray();
for (0..@intCast(db_res.rows())) |rowidx| { 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); const name = db_res.get([]const u8, @intCast(rowidx), 1);
try json_writer.beginObject(); try json_writer.beginObject();
try json_writer.objectField("id"); 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.objectField("name");
try json_writer.write(name); try json_writer.write(name);
try json_writer.endObject(); try json_writer.endObject();

View file

@ -1,10 +1,11 @@
const std = @import("std"); const std = @import("std");
const c = ffi.c; const c = ffi.c;
const UUID = @import("uuid").Uuid;
const ffi = @import("../../../ffi.zig"); const ffi = @import("../../../ffi.zig");
const conutil = @import("../../../conutil.zig"); const conutil = @import("../../../conutil.zig");
const Id = @import("../../../Id.zig");
const State = @import("../../../State.zig"); const State = @import("../../../State.zig");
pub fn matches(path: []const u8) bool { 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) if (sel_result.rows() != 1 or sel_result.cols() != 1)
return error.InvalidResultFromPostgresServer; return error.InvalidResultFromPostgresServer;
const userid = sel_result.get(Id, 0, 0); const userid = sel_result.get(UUID, 0, 0);
const Profile = struct { const Profile = struct {
name: []const u8, 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: { const client_token: [:0]const u8 = req_payload.value.clientToken orelse gentoken: {
// TODO: according to https://wiki.vg/Legacy_Mojang_Authentication, the normal server // 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. // 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; break :gentoken &gen_token_buf;
}; };
// remains valid for one week // remains valid for one week
const expiry = std.time.timestamp() + std.time.ms_per_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( const add_tok_stat = state.db.execParams(
\\INSERT INTO tokens (id, userid, expiry, client_token) \\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(); defer add_tok_stat.deinit();
try add_tok_stat.expectCommand(); try add_tok_stat.expectCommand();
const uid_hex = userid.toString(); const uid_hex = userid.toStringCompact();
const profile = Profile{ const profile = Profile{
.name = req_payload.value.username, .name = req_payload.value.username,
@ -158,7 +164,7 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void {
}, },
} else null, } else null,
.clientToken = client_token, .clientToken = client_token,
.accessToken = &tokenid.toString(), .accessToken = &tokenid.toStringCompact(),
.availableProfiles = &.{profile}, .availableProfiles = &.{profile},
.selectedProfile = profile, .selectedProfile = profile,
}; };

View file

@ -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, .{}); const json = try std.json.stringifyAlloc(state.allocator, response_payload, .{});
defer state.allocator.free(json); defer state.allocator.free(json);
try req.respond(json, .{ try req.respond(json, .{ .extra_headers = &.{.{
.extra_headers = &.{.{ .name = "Content-Type",
.name = "Content-Type", .value = "application/json",
.value = "application/json", }} });
}}
});
} }

View file

@ -1,10 +1,11 @@
const std = @import("std"); const std = @import("std");
const c = ffi.c; const c = ffi.c;
const UUID = @import("uuid").Uuid;
const conutil = @import("../../../../../conutil.zig"); const conutil = @import("../../../../../conutil.zig");
const ffi = @import("../../../../../ffi.zig"); const ffi = @import("../../../../../ffi.zig");
const Id = @import("../../../../../Id.zig");
const jsonUserWriter = @import("../../../../../json_user_writer.zig").jsonUserWriter; const jsonUserWriter = @import("../../../../../json_user_writer.zig").jsonUserWriter;
const State = @import("../../../../../State.zig"); 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.cols() != 1) return error.InvalidResultFromPostgresServer;
if (sel_dbret.rows() >= 1) { 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); const skin_url = try state.getSkinUrl(params.username);
defer if (skin_url) |url| state.allocator.free(url); defer if (skin_url) |url| state.allocator.free(url);

View file

@ -1,10 +1,11 @@
const std = @import("std"); const std = @import("std");
const c = ffi.c; const c = ffi.c;
const UUID = @import("uuid").Uuid;
const conutil = @import("../../../../../conutil.zig"); const conutil = @import("../../../../../conutil.zig");
const ffi = @import("../../../../../ffi.zig"); const ffi = @import("../../../../../ffi.zig");
const Id = @import("../../../../../Id.zig");
const State = @import("../../../../../State.zig"); const State = @import("../../../../../State.zig");
pub fn matches(path: []const u8) bool { 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(); defer req_payload.deinit();
const access_token = Id.parse(req_payload.value.accessToken) orelse { const access_token = UUID.fromString(req_payload.value.accessToken) catch {
try conutil.sendJsonError(req, .bad_request, "accessToken is not a valid ID!", .{}); try conutil.sendJsonError(req, .bad_request, "accessToken is not a valid UUID!", .{});
return; return;
}; };
const sel_profile = Id.parse(req_payload.value.selectedProfile) orelse { const sel_profile = UUID.fromString(req_payload.value.selectedProfile) catch {
try conutil.sendJsonError(req, .bad_request, "selectedProfile is not a valid ID!", .{}); try conutil.sendJsonError(req, .bad_request, "selectedProfile is not a valid UUID!", .{});
return; 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.cols() != 1) return error.InvalidResultFromPostgresServer;
if (dbret.rows() >= 1) { 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)) { if (std.mem.eql(u8, &sel_profile.bytes, &token_user.bytes)) {
const ins_dbret = state.db.execParams( const ins_dbret = state.db.execParams(
\\INSERT INTO joins (userid, serverid) \\INSERT INTO joins (userid, serverid)

View file

@ -1,10 +1,11 @@
const std = @import("std"); const std = @import("std");
const c = ffi.c; const c = ffi.c;
const UUID = @import("uuid").Uuid;
const ffi = @import("../../../../../ffi.zig"); const ffi = @import("../../../../../ffi.zig");
const conutil = @import("../../../../../conutil.zig"); const conutil = @import("../../../../../conutil.zig");
const Id = @import("../../../../../Id.zig");
const jsonUserWriter = @import("../../../../../json_user_writer.zig").jsonUserWriter; const jsonUserWriter = @import("../../../../../json_user_writer.zig").jsonUserWriter;
const State = @import("../../../../../State.zig"); 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); 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. // 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( try conutil.sendJsonError(
req, req,
.bad_request, .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..]}, .{req_url.path[path_prefix.len..]},
); );
return; return;