feat: implement support for capes
This commit is contained in:
parent
94d96f1657
commit
69832efce5
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
175
src/State.zig
175
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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
15
src/main.zig
15
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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in a new issue