feat: use UUID library
This commit is contained in:
parent
0166219065
commit
94d96f1657
12 changed files with 53 additions and 68 deletions
|
@ -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");
|
||||||
|
|
|
@ -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 = .{""},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.* = ¶m.bytes;
|
val.* = ¶m.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)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
30
src/Id.zig
30
src/Id.zig
|
@ -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 };
|
|
||||||
}
|
|
|
@ -89,4 +89,3 @@ pub fn parseQueryParametersOrDefaults(params: []const u8, comptime T: type) Quer
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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",
|
||||||
}}
|
}} });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue