Launch Android broker through Termux URL handler
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Android CLI 客户端
|
||||
|
||||
Termux 登录不让浏览器直接访问 Termux 的 `127.0.0.1`:CLI 通过 `/system/bin/am` 启动 APK,APK 注册 `com.nitload.charac://oauth/callback` 并接收 authentik 回调,再用 `RUN_COMMAND` 调用 `charac callback` 将授权码转回 CLI 的本地监听器。
|
||||
Termux 登录不让浏览器直接访问 Termux 的 `127.0.0.1`:CLI 通过 `termux-open-url` 启动 APK,APK 注册 `com.nitload.charac://oauth/callback` 并接收 authentik 回调,再用 `RUN_COMMAND` 调用 `charac callback` 将授权码转回 CLI 的本地监听器。
|
||||
|
||||
推荐把 Android CLI 作为 Termux DEB 分发,而不是把 CLI 当成普通 Android APK。APK 只是可选的 One UI 启动器:它不能单独运行 CLI,必须与 Termux、Termux DEB、运行命令权限和 `allow-external-apps=true` 配置一起使用。直接安装 APK 后点击图标不能替代 Termux 环境,也不会自动安装 .NET 或 `charac`。
|
||||
|
||||
|
||||
@@ -57,6 +57,24 @@ public sealed class MainActivity : Activity
|
||||
|
||||
private void HandleBrokerIntent(Intent intent)
|
||||
{
|
||||
var brokerUri = intent.Data;
|
||||
if (brokerUri?.Scheme == "com.nitload.charac" && brokerUri.Host == "oauth" &&
|
||||
brokerUri.Path == "/start")
|
||||
{
|
||||
var brokerAuthorizationUrl = brokerUri.GetQueryParameter("url");
|
||||
var portText = brokerUri.GetQueryParameter("port");
|
||||
if (brokerAuthorizationUrl is null || !Uri.TryCreate(brokerAuthorizationUrl, UriKind.Absolute, out var parsedUrl) ||
|
||||
parsedUrl.Scheme != Uri.UriSchemeHttps || !int.TryParse(portText, out var port) ||
|
||||
port is <= 0 or > 65535)
|
||||
{
|
||||
SetStatus("OIDC 启动参数无效。请从 Termux 重新发起登录。", false);
|
||||
return;
|
||||
}
|
||||
|
||||
StartBrowserForAuthorization(parsedUrl, port);
|
||||
return;
|
||||
}
|
||||
|
||||
if (intent.GetStringExtra(AuthUrlExtra) is { Length: > 0 } authorizationUrl)
|
||||
{
|
||||
var port = intent.GetIntExtra(CallbackPortExtra, 0);
|
||||
@@ -85,15 +103,11 @@ public sealed class MainActivity : Activity
|
||||
return;
|
||||
}
|
||||
updatedEditor.Apply();
|
||||
try
|
||||
{
|
||||
StartActivity(new Intent(Intent.ActionView, global::Android.Net.Uri.Parse(authorizationUrl)));
|
||||
SetStatus("已打开浏览器。完成登录后将自动返回 Termux。", false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
SetStatus("无法打开浏览器:" + exception.Message, false);
|
||||
}
|
||||
if (Uri.TryCreate(authorizationUrl, UriKind.Absolute, out var parsedUrl) &&
|
||||
parsedUrl.Scheme == Uri.UriSchemeHttps)
|
||||
StartBrowserForAuthorization(parsedUrl, port);
|
||||
else
|
||||
SetStatus("OIDC 授权地址无效。请从 Termux 重新发起登录。", false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -121,6 +135,33 @@ public sealed class MainActivity : Activity
|
||||
SetStatus("登录回调已交给 Termux。请返回终端等待完成。", true);
|
||||
}
|
||||
|
||||
private void StartBrowserForAuthorization(Uri authorizationUrl, int port)
|
||||
{
|
||||
var preferences = GetPreferences(FileCreationMode.Private);
|
||||
var editor = preferences?.Edit();
|
||||
if (editor is null)
|
||||
{
|
||||
SetStatus("无法保存 OIDC 回调状态。请从 Termux 重新发起登录。", false);
|
||||
return;
|
||||
}
|
||||
var updatedEditor = editor.PutInt(CallbackPortPreference, port);
|
||||
if (updatedEditor is null)
|
||||
{
|
||||
SetStatus("无法保存 OIDC 回调状态。请从 Termux 重新发起登录。", false);
|
||||
return;
|
||||
}
|
||||
updatedEditor.Apply();
|
||||
try
|
||||
{
|
||||
StartActivity(new Intent(Intent.ActionView, global::Android.Net.Uri.Parse(authorizationUrl.ToString())));
|
||||
SetStatus("已打开浏览器。完成登录后将自动返回 Termux。", false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
SetStatus("无法打开浏览器:" + exception.Message, false);
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasTermux()
|
||||
{
|
||||
try
|
||||
|
||||
+10
-12
@@ -9,6 +9,8 @@ namespace Charac.Client;
|
||||
|
||||
internal static class OidcLogin
|
||||
{
|
||||
private const string TermuxBin = "/data/data/com.termux/files/usr/bin";
|
||||
|
||||
public static async Task<string> LoginAsync(HttpClient client, string issuer, string clientId,
|
||||
string discoveryUrl,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -106,22 +108,18 @@ internal static class OidcLogin
|
||||
{
|
||||
try
|
||||
{
|
||||
var start = new ProcessStartInfo("/system/bin/am") { UseShellExecute = false };
|
||||
start.ArgumentList.Add("start");
|
||||
start.ArgumentList.Add("-n");
|
||||
start.ArgumentList.Add("com.nitload.charac/.MainActivity");
|
||||
start.ArgumentList.Add("--es");
|
||||
start.ArgumentList.Add("charac_auth_url");
|
||||
start.ArgumentList.Add(authorizationUri.ToString());
|
||||
start.ArgumentList.Add("--ei");
|
||||
start.ArgumentList.Add("charac_callback_port");
|
||||
start.ArgumentList.Add(port.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||
var brokerUri = new UriBuilder("com.nitload.charac://oauth/start")
|
||||
{
|
||||
Query = $"url={Uri.EscapeDataString(authorizationUri.ToString())}&port={port.ToString(System.Globalization.CultureInfo.InvariantCulture)}"
|
||||
}.Uri;
|
||||
var start = new ProcessStartInfo(TermuxBin + "/termux-open-url") { UseShellExecute = false };
|
||||
start.ArgumentList.Add(brokerUri.ToString());
|
||||
using var process = Process.Start(start);
|
||||
process?.WaitForExit(5000);
|
||||
if (process is null || process.ExitCode != 0)
|
||||
throw new InvalidOperationException("Android callback broker did not start.");
|
||||
throw new InvalidOperationException("Termux could not open the Android callback broker.");
|
||||
}
|
||||
catch (Exception exception) when (exception is System.ComponentModel.Win32Exception or InvalidOperationException)
|
||||
catch (Exception exception) when (exception is System.ComponentModel.Win32Exception or InvalidOperationException or System.IO.IOException)
|
||||
{
|
||||
Console.WriteLine($"Could not start the Android callback broker: {exception.Message}");
|
||||
Console.WriteLine("Open the URL manually in a browser on this device.");
|
||||
|
||||
Reference in New Issue
Block a user