Launch Android broker through Termux URL handler
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Android CLI 客户端
|
# 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`。
|
推荐把 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)
|
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)
|
if (intent.GetStringExtra(AuthUrlExtra) is { Length: > 0 } authorizationUrl)
|
||||||
{
|
{
|
||||||
var port = intent.GetIntExtra(CallbackPortExtra, 0);
|
var port = intent.GetIntExtra(CallbackPortExtra, 0);
|
||||||
@@ -85,15 +103,11 @@ public sealed class MainActivity : Activity
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updatedEditor.Apply();
|
updatedEditor.Apply();
|
||||||
try
|
if (Uri.TryCreate(authorizationUrl, UriKind.Absolute, out var parsedUrl) &&
|
||||||
{
|
parsedUrl.Scheme == Uri.UriSchemeHttps)
|
||||||
StartActivity(new Intent(Intent.ActionView, global::Android.Net.Uri.Parse(authorizationUrl)));
|
StartBrowserForAuthorization(parsedUrl, port);
|
||||||
SetStatus("已打开浏览器。完成登录后将自动返回 Termux。", false);
|
else
|
||||||
}
|
SetStatus("OIDC 授权地址无效。请从 Termux 重新发起登录。", false);
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
SetStatus("无法打开浏览器:" + exception.Message, false);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +135,33 @@ public sealed class MainActivity : Activity
|
|||||||
SetStatus("登录回调已交给 Termux。请返回终端等待完成。", true);
|
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()
|
private bool HasTermux()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
+10
-12
@@ -9,6 +9,8 @@ namespace Charac.Client;
|
|||||||
|
|
||||||
internal static class OidcLogin
|
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,
|
public static async Task<string> LoginAsync(HttpClient client, string issuer, string clientId,
|
||||||
string discoveryUrl,
|
string discoveryUrl,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
@@ -106,22 +108,18 @@ internal static class OidcLogin
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var start = new ProcessStartInfo("/system/bin/am") { UseShellExecute = false };
|
var brokerUri = new UriBuilder("com.nitload.charac://oauth/start")
|
||||||
start.ArgumentList.Add("start");
|
{
|
||||||
start.ArgumentList.Add("-n");
|
Query = $"url={Uri.EscapeDataString(authorizationUri.ToString())}&port={port.ToString(System.Globalization.CultureInfo.InvariantCulture)}"
|
||||||
start.ArgumentList.Add("com.nitload.charac/.MainActivity");
|
}.Uri;
|
||||||
start.ArgumentList.Add("--es");
|
var start = new ProcessStartInfo(TermuxBin + "/termux-open-url") { UseShellExecute = false };
|
||||||
start.ArgumentList.Add("charac_auth_url");
|
start.ArgumentList.Add(brokerUri.ToString());
|
||||||
start.ArgumentList.Add(authorizationUri.ToString());
|
|
||||||
start.ArgumentList.Add("--ei");
|
|
||||||
start.ArgumentList.Add("charac_callback_port");
|
|
||||||
start.ArgumentList.Add(port.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
|
||||||
using var process = Process.Start(start);
|
using var process = Process.Start(start);
|
||||||
process?.WaitForExit(5000);
|
process?.WaitForExit(5000);
|
||||||
if (process is null || process.ExitCode != 0)
|
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($"Could not start the Android callback broker: {exception.Message}");
|
||||||
Console.WriteLine("Open the URL manually in a browser on this device.");
|
Console.WriteLine("Open the URL manually in a browser on this device.");
|
||||||
|
|||||||
Reference in New Issue
Block a user