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" },
});
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");

View file

@ -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 = .{""},
}

View file

@ -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.* = &param.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)),
};
}

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

View file

@ -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");

View file

@ -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();

View file

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

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, .{});
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",
}} });
}

View file

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

View file

@ -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)

View file

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