me-web? 0.2: the test from outside, calls and games, mail, and what you can host
Phases two and three ship together, because their code grew in the same files. With consent the app asks the router to open 80, 443 and a high port for ten minutes (UPnP, NAT-PMP or PCP), and the server, holding a single-use ticket bound to the caller's own address, connects back and must get an HMAC only the app can give; the mappings are removed after the run and remembered until then, so a crash cannot leave one open. Hairpin is tried from inside. Two STUN servers, the outside one first, tell easy from hard NAT. Mail gets port 25 to Gmail and Outlook, the reverse name and five blocklists through the user's own resolver, network-wide UCEPROTECT listings counted as a caution. DNS, speed against the server (four at a time, twelve an hour per address) and the device follow, and six uses (website, home from outside, files, game server, fediverse, mail) close the run as possible, possible with a named workaround, or can't tell. Findings can be shared after reading the exact JSON: a file per submission under the unit's state directory, ten a day per address, deletable by the token the app keeps, swept after twelve months; /privacy says all of it in English and Italian. QR codes lead to the downloads. Every language carries every string. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
1 parent
f79682eaef
commit
7cfc8b6de5
161 files changed
+23159
-305
No files matched your search
@@ -12,15 +12,47 @@ public static class Endpoints
|
||||
|
||||
public const string Api = "/api";
|
||||
|
||||
public const string StunHost = "stun.meweb.arasaka.software";
|
||||
public const int StunPort = 3478;
|
||||
public const string SecondStunHost = "stun.nextcloud.com";
|
||||
public const int SecondStunPort = 443;
|
||||
|
||||
public static class Server
|
||||
{
|
||||
public const string GET_Info = $"{Api}/info";
|
||||
public const string GET_Ip = $"{Api}/ip";
|
||||
}
|
||||
|
||||
public static class Probe
|
||||
{
|
||||
public const string POST_Ticket = $"{Api}/probe/ticket";
|
||||
public const string POST_Run = $"{Api}/probe/{{id}}";
|
||||
|
||||
public static string POST_M_Run(string id) => $"{Api}/probe/{id}";
|
||||
}
|
||||
|
||||
public static class Speed
|
||||
{
|
||||
public const string GET_Down = $"{Api}/speed/down";
|
||||
public const string POST_Up = $"{Api}/speed/up";
|
||||
public const int MaxDownBytes = 25 * 1024 * 1024;
|
||||
public const int MaxUpBytes = 10 * 1024 * 1024;
|
||||
|
||||
public static string GET_M_Down(int bytes) => $"{Api}/speed/down?bytes={bytes}";
|
||||
}
|
||||
|
||||
public static class Share
|
||||
{
|
||||
public const string POST_Create = $"{Api}/share";
|
||||
public const string DELETE_One = $"{Api}/share/{{id}}";
|
||||
|
||||
public static string DELETE_M_One(string id) => $"{Api}/share/{id}";
|
||||
}
|
||||
|
||||
public static class Downloads
|
||||
{
|
||||
public const string GET_Page = "/";
|
||||
public const string GET_Privacy = "/privacy";
|
||||
public const string GET_Platform = "/download/{platform}";
|
||||
|
||||
public static string GET_M_Platform(string platform) => $"/download/{platform}";
|
||||
|
||||
@@ -3,9 +3,14 @@ using System.Text.Json.Serialization;
|
||||
|
||||
namespace MeWeb.Shared;
|
||||
|
||||
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web)]
|
||||
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web, UseStringEnumConverter = true)]
|
||||
[JsonSerializable(typeof(ViewInfo))]
|
||||
[JsonSerializable(typeof(ViewIp))]
|
||||
[JsonSerializable(typeof(ViewProbeTicket))]
|
||||
[JsonSerializable(typeof(ProbeForm))]
|
||||
[JsonSerializable(typeof(ViewProbe))]
|
||||
[JsonSerializable(typeof(ViewRefusal))]
|
||||
[JsonSerializable(typeof(ViewShared))]
|
||||
public partial class MeWebJsonContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
namespace MeWeb.Shared;
|
||||
|
||||
public enum ProbeOutcome
|
||||
{
|
||||
Reached,
|
||||
OpenNotYou,
|
||||
Refused,
|
||||
TimedOut,
|
||||
}
|
||||
|
||||
public class ViewProbeTicket
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Nonce { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string Address { get; set; }
|
||||
public int Seconds { get; set; }
|
||||
}
|
||||
|
||||
public class ProbeForm
|
||||
{
|
||||
public List<int> Ports { get; set; } = [];
|
||||
}
|
||||
|
||||
public class ViewProbePort
|
||||
{
|
||||
public int Port { get; set; }
|
||||
public ProbeOutcome Outcome { get; set; }
|
||||
}
|
||||
|
||||
public class ViewProbe
|
||||
{
|
||||
public string Address { get; set; }
|
||||
public List<ViewProbePort> Ports { get; set; } = [];
|
||||
}
|
||||
|
||||
public class ViewRefusal
|
||||
{
|
||||
public string Reason { get; set; }
|
||||
public int RetryAfterSeconds { get; set; }
|
||||
}
|
||||
|
||||
public static class ProbeProof
|
||||
{
|
||||
public const string Greeting = "MEWEB1";
|
||||
|
||||
public static string For(byte[] key, string nonce, int port) =>
|
||||
Convert.ToHexStringLower(System.Security.Cryptography.HMACSHA256.HashData(key, System.Text.Encoding.ASCII.GetBytes($"{nonce}|{port}")));
|
||||
}
|
||||
|
||||
public class ViewShared
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Token { get; set; }
|
||||
public string KeepUntil { get; set; }
|
||||
}
|
||||
Reference in new issue
Block a user