From 69832efce57b82c21918583d49b084da1215eaf1 Mon Sep 17 00:00:00 2001 From: LordMZTE Date: Wed, 6 Mar 2024 16:08:04 +0100 Subject: [PATCH] feat: implement support for capes --- conf.json | 4 +- src/Config.zig | 1 + src/State.zig | 175 ++++++++++++++---- src/json_user_writer.zig | 13 +- src/main.zig | 15 +- .../session/minecraft/has_joined.zig | 6 +- .../session/minecraft/profile.zig | 6 +- 7 files changed, 175 insertions(+), 45 deletions(-) diff --git a/conf.json b/conf.json index 8038e41..467f034 100644 --- a/conf.json +++ b/conf.json @@ -6,10 +6,12 @@ "base_url": "http://localhost:8081/", "postgres_url": "postgres://anvilauth:alec@localhost:5432/anvilauth", "forgejo_url": "https://git.tilera.org/", + "anvillib_url": "https://api.tilera.xyz/anvillib/", "skin_domains": [ "localhost", ".localhost", - "git.tilera.org" + "git.tilera.org", + "s3.tilera.xyz" ], "server_name": "AnvilAuth test server" } diff --git a/src/Config.zig b/src/Config.zig index 6e08753..2a3dbd8 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -5,5 +5,6 @@ bind: struct { base_url: []const u8, postgres_url: [:0]const u8, forgejo_url: []const u8, +anvillib_url: ?[]const u8, skin_domains: []const []const u8, server_name: []const u8, diff --git a/src/State.zig b/src/State.zig index ff3e176..c625907 100644 --- a/src/State.zig +++ b/src/State.zig @@ -1,13 +1,20 @@ const std = @import("std"); const c = @import("ffi.zig").c; +const UUID = @import("uuid").Uuid; + const Db = @import("Db.zig"); -pub const SkinCache = std.StringHashMapUnmanaged(struct { has_skin: bool, expiration: i64 }); +pub const UserCache = std.StringHashMapUnmanaged(struct { + skin_url: ?[]const u8, + cape_url: ?[]const u8, + expiration: i64, +}); allocator: std.mem.Allocator, base_url: []const u8, forgejo_url: []const u8, +anvillib_url: ?[]const u8, skin_domains: []const []const u8, server_name: []const u8, http: std.http.Client, @@ -16,49 +23,155 @@ rand: std.rand.Random, rsa: *c.RSA, x509: *c.X509, default_skin_url: []const u8, -skin_cache: SkinCache, -skin_cache_mtx: std.Thread.Mutex = .{}, +user_cache: UserCache, +user_cache_mtx: std.Thread.Mutex = .{}, const State = @This(); +pub const TextureUrls = struct { + skin_url: ?[]const u8, + cape_url: ?[]const u8, +}; + /// Gets the skin URL for a given user, if the user has a skin URL set. May do network IO for checking. -pub fn getSkinUrl(self: *State, username: []const u8) !?[]const u8 { - self.skin_cache_mtx.lock(); - defer self.skin_cache_mtx.unlock(); +pub fn getTextureUrls(self: *State, username: []const u8, uuid: UUID) !TextureUrls { + self.user_cache_mtx.lock(); + defer self.user_cache_mtx.unlock(); - const url = try std.fmt.allocPrint( - self.allocator, - "{s}/{s}/.anvilauth/raw/branch/master/skin.png", - .{ self.forgejo_url, username }, - ); - errdefer self.allocator.free(url); - - if (self.skin_cache.get(username)) |entry| { + if (self.user_cache.get(username)) |entry| { if (std.time.milliTimestamp() < entry.expiration) { - if (entry.has_skin) - return url; - - self.allocator.free(url); - return null; + return .{ + .skin_url = entry.skin_url, + .cape_url = entry.cape_url, + }; } + + if (entry.skin_url) |skin| self.allocator.free(skin); + if (entry.cape_url) |cape| self.allocator.free(cape); + std.debug.assert(self.user_cache.remove(username)); } - std.log.info("checking presence of custom skin for user '{s}'", .{username}); - const res = try self.http.fetch(.{ - .method = .HEAD, - .location = .{ .url = url }, - }); + std.log.info("checking presence of custom skin and cape for user '{s}'", .{username}); + + const skin_url = skin: { + const skin_url = try std.fmt.allocPrint( + self.allocator, + "{s}/{s}/.anvilauth/raw/branch/master/skin.png", + .{ self.forgejo_url, username }, + ); + errdefer self.allocator.free(skin_url); + + const skin_res = try self.http.fetch(.{ + .method = .HEAD, + .location = .{ .url = skin_url }, + }); + + if (skin_res.status == .ok) + break :skin skin_url; + + self.allocator.free(skin_url); + break :skin null; + }; + errdefer self.allocator.free(skin_url); + + const cape_url = cape: { + if (self.anvillib_url == null) break :cape null; + + const extra_headers = [_]std.http.Header{ + .{ .name = "X-AnvilLib-Version", .value = "0.2.0" }, + .{ .name = "X-Minecraft-Version", .value = "0.0.0-anvilauth" }, + }; + + var header_buf: [1024]u8 = undefined; + const cape_id = players: { + const players_url = try std.fmt.allocPrint( + self.allocator, + "{s}/data/players/{s}", + .{ self.anvillib_url.?, &uuid.toStringWithDashes() }, + ); + defer self.allocator.free(players_url); + + var players_req = try self.http.open( + .GET, + try std.Uri.parse(players_url), + .{ + .server_header_buffer = &header_buf, + .extra_headers = &extra_headers, + }, + ); + defer players_req.deinit(); + try players_req.send(.{}); + try players_req.wait(); + + if (players_req.response.status != .ok) { + break :players null; + } + + var players_json_reader = std.json.reader(self.allocator, players_req.reader()); + defer players_json_reader.deinit(); + + const players_parsed = try std.json.parseFromTokenSource( + struct { cape: ?[]const u8 }, + self.allocator, + &players_json_reader, + .{ .ignore_unknown_fields = true }, + ); + defer players_parsed.deinit(); + + break :players if (players_parsed.value.cape) |cape| + try self.allocator.dupe(u8, cape) + else + null; + }; + defer if (cape_id) |u| self.allocator.free(u); + + if (cape_id == null) break :cape null; + + const capes_url = try std.fmt.allocPrint( + self.allocator, + "{s}/data/capes/{s}", + .{ self.anvillib_url.?, cape_id.? }, + ); + defer self.allocator.free(capes_url); + + var capes_req = try self.http.open( + .GET, + try std.Uri.parse(capes_url), + .{ + .server_header_buffer = &header_buf, + .extra_headers = &extra_headers, + }, + ); + defer capes_req.deinit(); + + try capes_req.send(.{}); + try capes_req.wait(); + + var capes_json_reader = std.json.reader(self.allocator, capes_req.reader()); + defer capes_json_reader.deinit(); + + const capes_parsed = try std.json.parseFromTokenSource( + struct { url: []const u8 }, + self.allocator, + &capes_json_reader, + .{ .ignore_unknown_fields = true }, + ); + defer capes_parsed.deinit(); + + break :cape try self.allocator.dupe(u8, capes_parsed.value.url); + }; + errdefer if (cape_url) |cape| self.allocator.free(cape); const username_d = try self.allocator.dupe(u8, username); errdefer self.allocator.free(username_d); - try self.skin_cache.put(self.allocator, username_d, .{ - .has_skin = res.status == .ok, + try self.user_cache.putNoClobber(self.allocator, username_d, .{ + .cape_url = cape_url, + .skin_url = skin_url, .expiration = std.time.milliTimestamp() + std.time.ms_per_day, }); - if (res.status == .ok) - return url; - - self.allocator.free(url); - return null; + return .{ + .skin_url = skin_url, + .cape_url = cape_url, + }; } diff --git a/src/json_user_writer.zig b/src/json_user_writer.zig index f769a08..77cb5d9 100644 --- a/src/json_user_writer.zig +++ b/src/json_user_writer.zig @@ -91,6 +91,7 @@ pub fn JsonUserWriter(Writer: type) type { user_id: UUID, user_name: []const u8, skin_url: []const u8, + cape_url: ?[]const u8, ) !void { var json_data = std.ArrayList(u8).init(self.alloc); defer json_data.deinit(); @@ -103,16 +104,24 @@ pub fn JsonUserWriter(Writer: type) type { try write_stream.write(&user_id.toStringCompact()); try write_stream.objectField("profileName"); try write_stream.write(user_name); - try write_stream.objectField("textures"); { + try write_stream.objectField("textures"); try write_stream.beginObject(); - try write_stream.objectField("SKIN"); { + try write_stream.objectField("SKIN"); try write_stream.beginObject(); try write_stream.objectField("url"); try write_stream.write(skin_url); try write_stream.endObject(); } + + if (cape_url) |cape| { + try write_stream.objectField("CAPE"); + try write_stream.beginObject(); + try write_stream.objectField("url"); + try write_stream.write(cape); + try write_stream.endObject(); + } try write_stream.endObject(); } try write_stream.endObject(); diff --git a/src/main.zig b/src/main.zig index 0ac745c..d8e2823 100644 --- a/src/main.zig +++ b/src/main.zig @@ -110,6 +110,10 @@ pub fn main() !u8 { .allocator = alloc, .base_url = base_url, .forgejo_url = std.mem.trimRight(u8, config_parsed.value.forgejo_url, "/"), + .anvillib_url = if (config_parsed.value.anvillib_url) |alu| + std.mem.trimRight(u8, alu, "/") + else + null, .skin_domains = config_parsed.value.skin_domains, .server_name = config_parsed.value.server_name, .http = .{ .allocator = alloc }, @@ -118,15 +122,16 @@ pub fn main() !u8 { .rsa = rsa, .x509 = x509, .default_skin_url = default_skin_url, - .skin_cache = State.SkinCache{}, + .user_cache = State.UserCache{}, }; defer state.http.deinit(); defer { - var kiter = state.skin_cache.keyIterator(); - while (kiter.next()) |key| { - alloc.free(key.*); + var iter = state.user_cache.iterator(); + while (iter.next()) |kv| { + alloc.free(kv.key_ptr.*); + if (kv.value_ptr.cape_url) |cape| alloc.free(cape); } - state.skin_cache.deinit(alloc); + state.user_cache.deinit(alloc); } const addr = try std.net.Address.parseIp(config_parsed.value.bind.ip, config_parsed.value.bind.port); diff --git a/src/routes/aliapi/sessionserver/session/minecraft/has_joined.zig b/src/routes/aliapi/sessionserver/session/minecraft/has_joined.zig index 89ec780..166ae0a 100644 --- a/src/routes/aliapi/sessionserver/session/minecraft/has_joined.zig +++ b/src/routes/aliapi/sessionserver/session/minecraft/has_joined.zig @@ -40,8 +40,7 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { if (sel_dbret.rows() >= 1) { 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); + const texture_urls = try state.getTextureUrls(params.username, id); var profile_json = std.ArrayList(u8).init(state.allocator); defer profile_json.deinit(); @@ -50,7 +49,8 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { try uprofile.texturesProperty( id, params.username, - skin_url orelse state.default_skin_url, + texture_urls.skin_url orelse state.default_skin_url, + texture_urls.cape_url, ); try uprofile.finish(); diff --git a/src/routes/aliapi/sessionserver/session/minecraft/profile.zig b/src/routes/aliapi/sessionserver/session/minecraft/profile.zig index da259c4..d1384de 100644 --- a/src/routes/aliapi/sessionserver/session/minecraft/profile.zig +++ b/src/routes/aliapi/sessionserver/session/minecraft/profile.zig @@ -67,8 +67,7 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { if (status.rows() >= 1) { const username = status.get([]const u8, 0, 0); - const skin_url = try state.getSkinUrl(username); - defer if (skin_url) |url| state.allocator.free(url); + const texture_urls = try state.getTextureUrls(username, profile_id); var response_data = std.ArrayList(u8).init(state.allocator); defer response_data.deinit(); @@ -81,7 +80,8 @@ pub fn call(req: *std.http.Server.Request, state: *State) !void { try uprofile.texturesProperty( profile_id, username, - skin_url orelse state.default_skin_url, + texture_urls.skin_url orelse state.default_skin_url, + texture_urls.cape_url, ); try uprofile.finish();