yavsc/src/PostIt/PostIt.Android/MainActivity.cs

65 lines
2.4 KiB
C#
Raw Normal View History

PostIt: switch OIDC client from confidential (client_secret) to public (PKCE/JWT) PostIt is a desktop/mobile app talking to Yavsc.Org (https://yavsc.pschneider.fr) as an OIDC identity provider. The previous grant used the client_credentials flow with a client_secret embedded in postit-settings.json: this was both insecure (secret travels with the binary) and inappropriate for an interactive app (token had no user identity, so the API could not scope or audit). The new flow is Authorization Code + PKCE: * PostIt client (Settings/AuthenticationSettings.cs): the ClientSecret property is removed; GetOidcClientOptions now drops the secret and accepts an optional IBrowser supplied per-platform. * Settings.cs: new AndroidRedirectUri constant ('android://postit-signin') that the Android app uses; RedirectUri is no longer hard-coded in MainViewModel. * MainViewModel.cs: the manual discovery + client_credentials POST is replaced with OidcClient.LoginAsync (Authorization Code + PKCE). * Settings sample: Authority points at the real Yavsc.Org OP, not at a non-existent Keycloak-style realm path. * Yavsc.Org/Extensions/HostingExtensions.cs: the 'postit' client seed is now idempotent (MigratePostItClientToPublic) and detects legacy state on existing ConfigurationDb rows - flips RequireClientSecret=false, RequirePkce=true, drops any ClientSecret row, and replaces the legacy RedirectUris (https://yavsc.pschneider.fr/, yavsc://callback) with the current set (http://127.0.0.1:7890/, android://postit-signin). PostIt.Android: * MainActivity: explicit Name attribute so the activity alias can target a stable component; LaunchMode.SingleTask so the existing instance receives the deep-link Intent; OnNewIntent forwards the callback URI through AndroidOidcCallbackSink. * AndroidManifest.xml: activity-alias PostIt.Android.OidcCallbackActivity exposing scheme=android host=postit-signin to Android, so the OP redirect lands back in the running PostIt instance. The IdentityModel.OidcClient.Browser.SystemBrowser package and a thin AndroidSystemBrowser implementation are added in a follow-up so OidcClient.LoginAsync can actually drive Chrome Custom Tabs and consume AndroidOidcCallbackSink.
2026-06-20 17:16:07 +01:00
using Android.App;
2026-05-29 01:29:36 +01:00
using Android.Content.PM;
PostIt: switch OIDC client from confidential (client_secret) to public (PKCE/JWT) PostIt is a desktop/mobile app talking to Yavsc.Org (https://yavsc.pschneider.fr) as an OIDC identity provider. The previous grant used the client_credentials flow with a client_secret embedded in postit-settings.json: this was both insecure (secret travels with the binary) and inappropriate for an interactive app (token had no user identity, so the API could not scope or audit). The new flow is Authorization Code + PKCE: * PostIt client (Settings/AuthenticationSettings.cs): the ClientSecret property is removed; GetOidcClientOptions now drops the secret and accepts an optional IBrowser supplied per-platform. * Settings.cs: new AndroidRedirectUri constant ('android://postit-signin') that the Android app uses; RedirectUri is no longer hard-coded in MainViewModel. * MainViewModel.cs: the manual discovery + client_credentials POST is replaced with OidcClient.LoginAsync (Authorization Code + PKCE). * Settings sample: Authority points at the real Yavsc.Org OP, not at a non-existent Keycloak-style realm path. * Yavsc.Org/Extensions/HostingExtensions.cs: the 'postit' client seed is now idempotent (MigratePostItClientToPublic) and detects legacy state on existing ConfigurationDb rows - flips RequireClientSecret=false, RequirePkce=true, drops any ClientSecret row, and replaces the legacy RedirectUris (https://yavsc.pschneider.fr/, yavsc://callback) with the current set (http://127.0.0.1:7890/, android://postit-signin). PostIt.Android: * MainActivity: explicit Name attribute so the activity alias can target a stable component; LaunchMode.SingleTask so the existing instance receives the deep-link Intent; OnNewIntent forwards the callback URI through AndroidOidcCallbackSink. * AndroidManifest.xml: activity-alias PostIt.Android.OidcCallbackActivity exposing scheme=android host=postit-signin to Android, so the OP redirect lands back in the running PostIt instance. The IdentityModel.OidcClient.Browser.SystemBrowser package and a thin AndroidSystemBrowser implementation are added in a follow-up so OidcClient.LoginAsync can actually drive Chrome Custom Tabs and consume AndroidOidcCallbackSink.
2026-06-20 17:16:07 +01:00
using Android.Content;
2026-05-29 01:29:36 +01:00
using Avalonia.Android;
namespace PostIt.Android;
[Activity(
PostIt: switch OIDC client from confidential (client_secret) to public (PKCE/JWT) PostIt is a desktop/mobile app talking to Yavsc.Org (https://yavsc.pschneider.fr) as an OIDC identity provider. The previous grant used the client_credentials flow with a client_secret embedded in postit-settings.json: this was both insecure (secret travels with the binary) and inappropriate for an interactive app (token had no user identity, so the API could not scope or audit). The new flow is Authorization Code + PKCE: * PostIt client (Settings/AuthenticationSettings.cs): the ClientSecret property is removed; GetOidcClientOptions now drops the secret and accepts an optional IBrowser supplied per-platform. * Settings.cs: new AndroidRedirectUri constant ('android://postit-signin') that the Android app uses; RedirectUri is no longer hard-coded in MainViewModel. * MainViewModel.cs: the manual discovery + client_credentials POST is replaced with OidcClient.LoginAsync (Authorization Code + PKCE). * Settings sample: Authority points at the real Yavsc.Org OP, not at a non-existent Keycloak-style realm path. * Yavsc.Org/Extensions/HostingExtensions.cs: the 'postit' client seed is now idempotent (MigratePostItClientToPublic) and detects legacy state on existing ConfigurationDb rows - flips RequireClientSecret=false, RequirePkce=true, drops any ClientSecret row, and replaces the legacy RedirectUris (https://yavsc.pschneider.fr/, yavsc://callback) with the current set (http://127.0.0.1:7890/, android://postit-signin). PostIt.Android: * MainActivity: explicit Name attribute so the activity alias can target a stable component; LaunchMode.SingleTask so the existing instance receives the deep-link Intent; OnNewIntent forwards the callback URI through AndroidOidcCallbackSink. * AndroidManifest.xml: activity-alias PostIt.Android.OidcCallbackActivity exposing scheme=android host=postit-signin to Android, so the OP redirect lands back in the running PostIt instance. The IdentityModel.OidcClient.Browser.SystemBrowser package and a thin AndroidSystemBrowser implementation are added in a follow-up so OidcClient.LoginAsync can actually drive Chrome Custom Tabs and consume AndroidOidcCallbackSink.
2026-06-20 17:16:07 +01:00
Name = "PostIt.Android.PostItMainActivity",
2026-05-29 01:29:36 +01:00
Label = "PostIt.Android",
Theme = "@style/MyTheme.NoActionBar",
Icon = "@drawable/icon",
MainLauncher = true,
PostIt: switch OIDC client from confidential (client_secret) to public (PKCE/JWT) PostIt is a desktop/mobile app talking to Yavsc.Org (https://yavsc.pschneider.fr) as an OIDC identity provider. The previous grant used the client_credentials flow with a client_secret embedded in postit-settings.json: this was both insecure (secret travels with the binary) and inappropriate for an interactive app (token had no user identity, so the API could not scope or audit). The new flow is Authorization Code + PKCE: * PostIt client (Settings/AuthenticationSettings.cs): the ClientSecret property is removed; GetOidcClientOptions now drops the secret and accepts an optional IBrowser supplied per-platform. * Settings.cs: new AndroidRedirectUri constant ('android://postit-signin') that the Android app uses; RedirectUri is no longer hard-coded in MainViewModel. * MainViewModel.cs: the manual discovery + client_credentials POST is replaced with OidcClient.LoginAsync (Authorization Code + PKCE). * Settings sample: Authority points at the real Yavsc.Org OP, not at a non-existent Keycloak-style realm path. * Yavsc.Org/Extensions/HostingExtensions.cs: the 'postit' client seed is now idempotent (MigratePostItClientToPublic) and detects legacy state on existing ConfigurationDb rows - flips RequireClientSecret=false, RequirePkce=true, drops any ClientSecret row, and replaces the legacy RedirectUris (https://yavsc.pschneider.fr/, yavsc://callback) with the current set (http://127.0.0.1:7890/, android://postit-signin). PostIt.Android: * MainActivity: explicit Name attribute so the activity alias can target a stable component; LaunchMode.SingleTask so the existing instance receives the deep-link Intent; OnNewIntent forwards the callback URI through AndroidOidcCallbackSink. * AndroidManifest.xml: activity-alias PostIt.Android.OidcCallbackActivity exposing scheme=android host=postit-signin to Android, so the OP redirect lands back in the running PostIt instance. The IdentityModel.OidcClient.Browser.SystemBrowser package and a thin AndroidSystemBrowser implementation are added in a follow-up so OidcClient.LoginAsync can actually drive Chrome Custom Tabs and consume AndroidOidcCallbackSink.
2026-06-20 17:16:07 +01:00
LaunchMode = LaunchMode.SingleTask,
2026-05-29 01:29:36 +01:00
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
public class MainActivity : AvaloniaMainActivity
{
/// <summary>
/// Strongly-typed handle to the current MainActivity instance, set in
/// <see cref="OnCreate"/> and consumed by platform services such as
/// <see cref="Services.AndroidSystemBrowser"/> which need to launch
/// Chrome Custom Tabs.
/// </summary>
public static MainActivity? Current { get; private set; }
protected override void OnCreate(global::Android.OS.Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
PlatformBootstrap.EnsureInitialized();
Current = this;
}
PostIt: switch OIDC client from confidential (client_secret) to public (PKCE/JWT) PostIt is a desktop/mobile app talking to Yavsc.Org (https://yavsc.pschneider.fr) as an OIDC identity provider. The previous grant used the client_credentials flow with a client_secret embedded in postit-settings.json: this was both insecure (secret travels with the binary) and inappropriate for an interactive app (token had no user identity, so the API could not scope or audit). The new flow is Authorization Code + PKCE: * PostIt client (Settings/AuthenticationSettings.cs): the ClientSecret property is removed; GetOidcClientOptions now drops the secret and accepts an optional IBrowser supplied per-platform. * Settings.cs: new AndroidRedirectUri constant ('android://postit-signin') that the Android app uses; RedirectUri is no longer hard-coded in MainViewModel. * MainViewModel.cs: the manual discovery + client_credentials POST is replaced with OidcClient.LoginAsync (Authorization Code + PKCE). * Settings sample: Authority points at the real Yavsc.Org OP, not at a non-existent Keycloak-style realm path. * Yavsc.Org/Extensions/HostingExtensions.cs: the 'postit' client seed is now idempotent (MigratePostItClientToPublic) and detects legacy state on existing ConfigurationDb rows - flips RequireClientSecret=false, RequirePkce=true, drops any ClientSecret row, and replaces the legacy RedirectUris (https://yavsc.pschneider.fr/, yavsc://callback) with the current set (http://127.0.0.1:7890/, android://postit-signin). PostIt.Android: * MainActivity: explicit Name attribute so the activity alias can target a stable component; LaunchMode.SingleTask so the existing instance receives the deep-link Intent; OnNewIntent forwards the callback URI through AndroidOidcCallbackSink. * AndroidManifest.xml: activity-alias PostIt.Android.OidcCallbackActivity exposing scheme=android host=postit-signin to Android, so the OP redirect lands back in the running PostIt instance. The IdentityModel.OidcClient.Browser.SystemBrowser package and a thin AndroidSystemBrowser implementation are added in a follow-up so OidcClient.LoginAsync can actually drive Chrome Custom Tabs and consume AndroidOidcCallbackSink.
2026-06-20 17:16:07 +01:00
/// <summary>
/// Receives the deep-link Intent fired by the system browser after the
/// user completes the OIDC login on https://yavsc.pschneider.fr. The
/// Intent URI has the shape <c>android://postit-signin?code=...&amp;state=...</c>.
///
/// IdentityModel.OidcClient.Browser.SystemBrowser is set up to await this
/// callback via a TaskCompletionSource; expose the received Intent here
/// through a static sink so the browser can resolve the pending login.
/// </summary>
protected override void OnNewIntent(Intent? intent)
{
base.OnNewIntent(intent);
if (intent is not null) AndroidOidcCallbackSink.Handle(intent);
}
internal static class AndroidOidcCallbackSink
{
private static System.Threading.Tasks.TaskCompletionSource<string>? _pending;
public static System.Threading.Tasks.Task<string> AwaitNextCallbackAsync()
{
_pending = new System.Threading.Tasks.TaskCompletionSource<string>(
System.Threading.Tasks.TaskCreationOptions.RunContinuationsAsynchronously);
return _pending.Task;
}
public static void Handle(Intent intent)
{
var tcs = System.Threading.Interlocked.Exchange(ref _pending, null);
tcs?.TrySetResult(intent?.Data?.ToString() ?? string.Empty);
}
}
2026-08-22 03:34:48 +01:00
}