The Online API now requires the "user" project role (claim urn:zitadel:iam:org:project:roles) instead of an ALLOWED_USER_IDS allowlist. - IOnlineAuthProvider: add GetAccessTokenAsync(forceRefresh) overload - ZitadelAuthProvider: forceRefresh drops the cached token and re-runs the refresh-token grant to mint a fresh, role-bearing token - OnlineInboxApiClient: on 401, force-refresh and retry once; if still 401, throw a clear "missing 'user' role" error - OnlineSyncService: surface the 401 at Error level (no longer silent) - UI: ZitadelTokenInspector decodes the access token after login and warns early when the "user" role is absent (fail-open); shown in settings - docs: online-inbox-api-contract reflects role-based access (no allowlist) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
124 lines
3.7 KiB
C#
124 lines
3.7 KiB
C#
using ClaudeDo.Ui.Localization;
|
|
using ClaudeDo.Ui.Services;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
|
|
namespace ClaudeDo.Ui.ViewModels.Modals.Settings;
|
|
|
|
public sealed partial class OnlineInboxSettingsViewModel : ViewModelBase
|
|
{
|
|
private readonly IWorkerClient _worker;
|
|
private readonly IOnlineLoginService _loginService;
|
|
|
|
[ObservableProperty] private bool _enabled;
|
|
[ObservableProperty] private string _apiBaseUrl = "";
|
|
[ObservableProperty] private string _authority = "";
|
|
[ObservableProperty] private string _clientId = "";
|
|
[ObservableProperty] private string _scopes = "openid offline_access";
|
|
[ObservableProperty] private string _redirectUri = "http://localhost:8765/callback";
|
|
[ObservableProperty] private int _pollIntervalSeconds = 60;
|
|
[ObservableProperty] private bool _signedIn;
|
|
[ObservableProperty] private bool _isBusy;
|
|
[ObservableProperty] private string _statusMessage = "";
|
|
|
|
public OnlineInboxSettingsViewModel(IWorkerClient worker, IOnlineLoginService loginService)
|
|
{
|
|
_worker = worker;
|
|
_loginService = loginService;
|
|
}
|
|
|
|
public async Task LoadAsync()
|
|
{
|
|
IsBusy = true;
|
|
StatusMessage = "";
|
|
try
|
|
{
|
|
var dto = await _worker.GetOnlineInboxStateAsync();
|
|
if (dto is null)
|
|
{
|
|
StatusMessage = Loc.T("vm.onlineInbox.workerOffline");
|
|
return;
|
|
}
|
|
|
|
Enabled = dto.Enabled;
|
|
ApiBaseUrl = dto.ApiBaseUrl;
|
|
Authority = dto.Authority;
|
|
ClientId = dto.ClientId;
|
|
Scopes = dto.Scopes;
|
|
RedirectUri = dto.RedirectUri;
|
|
SignedIn = dto.SignedIn;
|
|
PollIntervalSeconds = dto.PollIntervalSeconds;
|
|
}
|
|
finally { IsBusy = false; }
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task Save()
|
|
{
|
|
IsBusy = true;
|
|
StatusMessage = "";
|
|
try
|
|
{
|
|
await _worker.SetOnlineInboxConfigAsync(new OnlineInboxConfigInputDto(
|
|
Enabled,
|
|
ApiBaseUrl,
|
|
PollIntervalSeconds,
|
|
Authority,
|
|
ClientId,
|
|
Scopes,
|
|
RedirectUri));
|
|
StatusMessage = Loc.T("vm.onlineInbox.saved");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StatusMessage = Loc.T("vm.onlineInbox.saveFailed", ex.Message);
|
|
}
|
|
finally { IsBusy = false; }
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task SignIn()
|
|
{
|
|
IsBusy = true;
|
|
StatusMessage = "";
|
|
try
|
|
{
|
|
var result = await _loginService.LoginAsync(Authority, ClientId, Scopes, RedirectUri);
|
|
if (!result.Success)
|
|
{
|
|
StatusMessage = Loc.T("vm.onlineInbox.signInFailed", result.Error ?? "Unknown error");
|
|
return;
|
|
}
|
|
|
|
await _worker.SetOnlineInboxAuthAsync(result.RefreshToken!);
|
|
SignedIn = true;
|
|
StatusMessage = result.Warning == "missing-user-role"
|
|
? Loc.T("vm.onlineInbox.signedInNoRole")
|
|
: Loc.T("vm.onlineInbox.signedIn");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StatusMessage = Loc.T("vm.onlineInbox.signInFailed", ex.Message);
|
|
}
|
|
finally { IsBusy = false; }
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task SignOut()
|
|
{
|
|
IsBusy = true;
|
|
StatusMessage = "";
|
|
try
|
|
{
|
|
await _worker.ClearOnlineInboxAuthAsync();
|
|
SignedIn = false;
|
|
StatusMessage = Loc.T("vm.onlineInbox.signedOut");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StatusMessage = Loc.T("vm.onlineInbox.signOutFailed", ex.Message);
|
|
}
|
|
finally { IsBusy = false; }
|
|
}
|
|
}
|