using PrivaPub.Infrastructure.Cli; using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json.Nodes; using System.Text.RegularExpressions; namespace PrivaPub.Tests.Support.Host { public sealed record Root(string Id, string UserName, string Password, string Jwt); public sealed record Persona(string Id, string UserName, Root Root); public static partial class Accounts { public const string Password = "Test-Pass-1!"; const string OutOfBand = "urn:ietf:wg:oauth:2.0:oob"; public static async Task SignUp(this PrivaPubHost host, string name = "root") { var userName = $"{name}{Guid.NewGuid():N}"[..24]; using var client = host.Client(); var response = await client.PostAsJsonAsync("/clientapi/user/signup", new { userName, password = Password }); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var jwt = (await response.Content.ReadFromJsonAsync())!; return new Root(jwt["userId"]!.GetValue(), userName, Password, jwt["token"]!.GetValue()); } public static async Task LogIn(this PrivaPubHost host, Root root) { using var client = host.Client(); var response = await client.PostAsJsonAsync("/clientapi/user/login", new { userName = root.UserName, password = root.Password }); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var jwt = (await response.Content.ReadFromJsonAsync())!; return root with { Jwt = jwt["token"]!.GetValue() }; } public static async Task Admin(this PrivaPubHost host) { var root = await host.SignUp("admin"); Assert.Equal(0, await AdminCommands.Run(new[] { "promote", root.UserName }, host.Services)); return await host.LogIn(root); } public static async Task Persona(this PrivaPubHost host, Root root, string name = "persona") { var userName = $"{name}{Guid.NewGuid():N}"[..20]; using var client = host.As(root.Jwt); var response = await client.PostAsJsonAsync("/clientapi/avatar/private/insert", new { userName, name, biography = "testing" }); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var avatar = (await response.Content.ReadFromJsonAsync())!; return new Persona(avatar["id"]!.GetValue(), userName, root); } public static async Task MastodonToken(this PrivaPubHost host, Persona persona, string scopes = "read write follow") { using var client = host.Client(cookies: true); var app = await Form(client, "/api/v1/apps", ("client_name", "privapub-tests"), ("redirect_uris", OutOfBand), ("scopes", scopes)); var clientId = app["client_id"]!.GetValue(); var clientSecret = app["client_secret"]!.GetValue(); var query = $"client_id={Uri.EscapeDataString(clientId)}&redirect_uri={Uri.EscapeDataString(OutOfBand)}&response_type=code&scope={Uri.EscapeDataString(scopes)}"; var code = await Authorize(client, persona, query); var token = await Form(client, "/oauth/token", ("grant_type", "authorization_code"), ("code", code), ("client_id", clientId), ("client_secret", clientSecret), ("redirect_uri", OutOfBand)); return token["access_token"]!.GetValue(); } public static async Task Authorize(HttpClient client, Persona persona, string query, string decision = "allow") { var returnUrl = "/oauth/authorize?" + query; var login = await client.GetStringAsync("/oauth/login?returnUrl=" + Uri.EscapeDataString(returnUrl)); var antiforgery = AntiforgeryToken().Match(login).Groups[1].Value; var signedIn = await client.PostAsync("/oauth/login", new FormUrlEncodedContent(new Dictionary { ["returnUrl"] = returnUrl, ["__RequestVerificationToken"] = antiforgery, ["userName"] = persona.Root.UserName, ["password"] = persona.Root.Password })); Assert.Equal(HttpStatusCode.Redirect, signedIn.StatusCode); var choose = await client.GetStringAsync(returnUrl + "&signed_in=1"); var fields = HiddenInput().Matches(choose).Select(m => new KeyValuePair(m.Groups[1].Value, WebUtility.HtmlDecode(m.Groups[2].Value))).ToList(); fields.Add(new("avatarId", persona.Id)); fields.Add(new("decision", decision)); var answer = await client.PostAsync("/oauth/authorize", new FormUrlEncodedContent(fields)); var page = await answer.Content.ReadAsStringAsync(); return Code().Match(page) is { Success: true } match ? match.Groups[1].Value : default; } public static HttpClient As(this PrivaPubHost host, string bearer) { var client = host.Client(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearer); return client; } static async Task Form(HttpClient client, string path, params (string Key, string Value)[] fields) { var response = await client.PostAsync(path, new FormUrlEncodedContent(fields.Select(f => new KeyValuePair(f.Key, f.Value)))); var body = await response.Content.ReadAsStringAsync(); Assert.True(response.IsSuccessStatusCode, $"{path} answered {(int)response.StatusCode}: {body}"); return JsonNode.Parse(body)!.AsObject(); } [GeneratedRegex("name=\"__RequestVerificationToken\" type=\"hidden\" value=\"([^\"]*)\"")] private static partial Regex AntiforgeryToken(); [GeneratedRegex("([^<]*)")] private static partial Regex Code(); } }