using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using TelegramAuthBot.Models; namespace TelegramAuthBot.Services { sealed class LampacTelegramAuthHttpClient : IDisposable { public const string MutationsSecretHeaderName = "X-TelegramAuth-Mutations-Secret"; readonly HttpClient _http; readonly string _mutationsSecret; public LampacTelegramAuthHttpClient(TelegramAuthBotConf conf) { var baseUrl = (conf.lampac_base_url ?? "").Trim().TrimEnd('/') + "/"; _http = new HttpClient { BaseAddress = new Uri(baseUrl, UriKind.Absolute) }; _http.Timeout = TimeSpan.FromSeconds(Math.Max(1, conf.request_timeout_sec)); _http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _mutationsSecret = conf.mutations_api_secret?.Trim() ?? ""; } public void Dispose() => _http.Dispose(); public async Task GetUserByTelegramAsync(string telegramId, CancellationToken ct) { var path = "tg/auth/user/by-telegram?" + Uri.EscapeDataString("telegramId") + "=" + Uri.EscapeDataString(telegramId); using var resp = await _http.GetAsync(path, ct).ConfigureAwait(false); var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); if (!resp.IsSuccessStatusCode) return null; return JsonConvert.DeserializeObject(body); } public async Task GetDevicesAsync(string telegramId, CancellationToken ct) { var path = "tg/auth/devices?" + Uri.EscapeDataString("telegramId") + "=" + Uri.EscapeDataString(telegramId); using var resp = await _http.GetAsync(path, ct).ConfigureAwait(false); var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); if (resp.StatusCode == HttpStatusCode.NotFound) return null; if (!resp.IsSuccessStatusCode) return null; return JsonConvert.DeserializeObject(body); } public async Task BindCompleteAsync(string uid, string telegramId, string username, CancellationToken ct) { var payload = new { uid, telegramId, username }; using var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json"); using var req = new HttpRequestMessage(HttpMethod.Post, "tg/auth/bind/complete") { Content = content }; AddMutationsSecret(req); using var resp = await _http.SendAsync(req, ct).ConfigureAwait(false); var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); if (!resp.IsSuccessStatusCode) return new BindCompleteResult(); var jo = JObject.Parse(body); return new BindCompleteResult { Ok = jo.Value("ok") == true, PendingAdminApproval = jo.Value("pendingAdminApproval") == true }; } public async Task UnbindDeviceAsync(string telegramId, string uid, CancellationToken ct) { var payload = new { telegramId, uid }; using var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json"); using var resp = await _http.PostAsync("tg/auth/device/unbind", content, ct).ConfigureAwait(false); var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); if (!resp.IsSuccessStatusCode) return false; var jo = JObject.Parse(body); return jo.Value("ok") == true; } public async Task<(bool ok, string detail)> ReactivateDeviceAsync(string telegramId, string uid, CancellationToken ct) { var payload = new { telegramId, uid }; using var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json"); using var resp = await _http.PostAsync("tg/auth/device/reactivate", content, ct).ConfigureAwait(false); var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); return resp.IsSuccessStatusCode ? (true, body) : (false, body); } public async Task<(bool ok, string detail)> SetDeviceDisplayNameAsync(string uid, string name, CancellationToken ct) { var payload = new { uid, name }; using var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json"); using var resp = await _http.PostAsync("tg/auth/device/name", content, ct).ConfigureAwait(false); var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); return resp.IsSuccessStatusCode ? (true, body) : (false, body); } public async Task<(bool ok, string detail)> ImportLegacyAsync(CancellationToken ct) { using var req = new HttpRequestMessage(HttpMethod.Post, "tg/auth/import"); AddMutationsSecret(req); using var resp = await _http.SendAsync(req, ct).ConfigureAwait(false); var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); return resp.IsSuccessStatusCode ? (true, body) : (false, body); } public async Task<(bool ok, string detail)> CleanupDevicesAsync(CancellationToken ct) { using var req = new HttpRequestMessage(HttpMethod.Post, "tg/auth/devices/cleanup"); AddMutationsSecret(req); using var resp = await _http.SendAsync(req, ct).ConfigureAwait(false); var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); return resp.IsSuccessStatusCode ? (true, body) : (false, body); } public async Task GetAdminUsersAsync(CancellationToken ct) { using var req = new HttpRequestMessage(HttpMethod.Get, "tg/auth/admin/users"); AddMutationsSecret(req); using var resp = await _http.SendAsync(req, ct).ConfigureAwait(false); var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); if (!resp.IsSuccessStatusCode) return null; return JsonConvert.DeserializeObject(body); } public async Task<(bool ok, string detail)> SetUserDisabledAsync(string telegramId, bool disabled, CancellationToken ct) { var payload = new { telegramId, disabled }; using var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json"); using var req = new HttpRequestMessage(HttpMethod.Post, "tg/auth/admin/user/disabled") { Content = content }; AddMutationsSecret(req); using var resp = await _http.SendAsync(req, ct).ConfigureAwait(false); var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); return resp.IsSuccessStatusCode ? (true, body) : (false, body); } public async Task<(bool ok, string detail)> ResolveRegistrationPendingAsync(string telegramId, bool approve, CancellationToken ct) { var payload = new { telegramId, approve }; using var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json"); using var req = new HttpRequestMessage(HttpMethod.Post, "tg/auth/admin/user/pending") { Content = content }; AddMutationsSecret(req); using var resp = await _http.SendAsync(req, ct).ConfigureAwait(false); var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); return resp.IsSuccessStatusCode ? (true, body) : (false, body); } public async Task<(bool ok, JObject data, string errorBody)> GetAdminUserDetailAsync(string telegramId, CancellationToken ct) { var path = "tg/auth/admin/user?" + Uri.EscapeDataString("telegramId") + "=" + Uri.EscapeDataString(telegramId); using var req = new HttpRequestMessage(HttpMethod.Get, path); AddMutationsSecret(req); using var resp = await _http.SendAsync(req, ct).ConfigureAwait(false); var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); if (!resp.IsSuccessStatusCode) return (false, null, body); try { return (true, JObject.Parse(body), body); } catch { return (false, null, body); } } public async Task<(bool ok, string detail)> PatchAdminUserAsync(JObject patch, CancellationToken ct) { using var content = new StringContent(patch.ToString(Formatting.None), Encoding.UTF8, "application/json"); using var req = new HttpRequestMessage(HttpMethod.Post, "tg/auth/admin/user/patch") { Content = content }; AddMutationsSecret(req); using var resp = await _http.SendAsync(req, ct).ConfigureAwait(false); var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); return resp.IsSuccessStatusCode ? (true, body) : (false, body); } void AddMutationsSecret(HttpRequestMessage req) { if (_mutationsSecret.Length > 0) req.Headers.TryAddWithoutValidation(MutationsSecretHeaderName, _mutationsSecret); } } }