@page "/profiles" @attribute [Microsoft.AspNetCore.Authorization.Authorize] @inject IUserSessionService Session @inject IUserContextAccessor UserContext @inject SharepointToolbox.Web.Infrastructure.Persistence.ProfileRepository ProfileRepo @inject ISessionCredentialStore CredStore @inject NavigationManager Nav @inject SharepointToolbox.Web.Services.OAuth.IOAuthFlowCache OAuthCache @rendermode InteractiveServer @using Microsoft.AspNetCore.WebUtilities @using SharepointToolbox.Web.Core.Models @using SharepointToolbox.Web.Services.Session

Client Profiles

Manage SharePoint tenant connections. Credentials are entered per session — no secrets stored on disk.

@if (UserContext.Role != UserRole.Admin) { @* Non-admins can only select a profile, not create/edit/delete *@
Profile management is restricted to Admins. Select a profile below to work on a client.
@foreach (var p in _profiles) {
@p.Name
@p.TenantUrl
@if (Session.CurrentProfile?.Id == p.Id) { Active }
} return; } @* Admin view — full CRUD *@ @if (!string.IsNullOrEmpty(_pageError)) {
@_pageError
}
@if (_profiles.Count == 0 && !_showForm) {
No profiles configured. Create one to get started.
} @foreach (var p in _profiles) {
@p.Name
@p.TenantUrl
Tenant ID: @p.TenantId
Client ID: @p.ClientId
@if (Session.CurrentProfile?.Id == p.Id) { Active }
} @if (_showForm) {
@(_editing?.Id == null ? "New Profile" : "Edit Profile")
@if (!string.IsNullOrEmpty(_formError)) {
@_formError
}
@* App registration section *@
Click "Register in Entra" to auto-create the app registration in the client tenant — requires Global Admin credentials. Or enter an existing public client App Registration ID manually.
} @code { private List _profiles = new(); private bool _showForm; private bool _registering; private TenantProfile? _editing; private TenantProfile _form = new(); private string _formError = string.Empty; private string _pageError = string.Empty; private bool CanRegister => !string.IsNullOrWhiteSpace(_form.Name) && !string.IsNullOrWhiteSpace(_form.TenantUrl) && !string.IsNullOrWhiteSpace(_form.TenantId); protected override async Task OnInitializedAsync() { _profiles = (await ProfileRepo.LoadAsync()).ToList(); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) return; await HandleRegResultAsync(); await HandleConnectErrorAsync(); } private async Task HandleRegResultAsync() { var uri = new Uri(Nav.Uri); var query = QueryHelpers.ParseQuery(uri.Query); if (!query.TryGetValue("reg_result_key", out var key) || string.IsNullOrEmpty(key)) return; var result = OAuthCache.GetAndRemoveRegistrationResult(key!); if (result is not null) { _form = new TenantProfile { Name = result.TenantName, TenantUrl = result.TenantUrl, TenantId = result.TenantId, ClientId = result.ClientId, }; _showForm = true; _formError = string.Empty; await InvokeAsync(StateHasChanged); } Nav.NavigateTo(uri.GetLeftPart(UriPartial.Path), replace: true); } private async Task HandleConnectErrorAsync() { var uri = new Uri(Nav.Uri); var query = QueryHelpers.ParseQuery(uri.Query); if (!query.TryGetValue("connect_error", out var err) || string.IsNullOrEmpty(err)) return; _pageError = err!; await InvokeAsync(StateHasChanged); Nav.NavigateTo(uri.GetLeftPart(UriPartial.Path), replace: true); } private void AddNew() { _editing = null; _form = new TenantProfile(); _showForm = true; _formError = string.Empty; _pageError = string.Empty; } private void EditProfile(TenantProfile p) { _editing = p; _form = new TenantProfile { Id = p.Id, Name = p.Name, TenantUrl = p.TenantUrl, TenantId = p.TenantId, ClientId = p.ClientId }; _showForm = true; _formError = _pageError = string.Empty; } private void CancelForm() { _showForm = false; _editing = null; } private void SelectProfile(TenantProfile p) { Session.SetProfile(p); StateHasChanged(); } private async Task RegisterAppAsync() { if (!CanRegister) return; _registering = true; StateHasChanged(); var returnUrl = Nav.Uri.Contains('?') ? Nav.Uri.Substring(0, Nav.Uri.IndexOf('?')) : Nav.Uri; var url = $"/connect/register-initiate" + $"?tenantId={Uri.EscapeDataString(_form.TenantId)}" + $"&tenantName={Uri.EscapeDataString(_form.Name)}" + $"&tenantUrl={Uri.EscapeDataString(_form.TenantUrl)}" + $"&returnUrl={Uri.EscapeDataString(returnUrl)}"; Nav.NavigateTo(url, forceLoad: true); } private async Task SaveProfile() { _formError = string.Empty; if (string.IsNullOrWhiteSpace(_form.Name)) { _formError = "Name is required."; return; } if (string.IsNullOrWhiteSpace(_form.TenantUrl)) { _formError = "Tenant URL is required."; return; } if (string.IsNullOrWhiteSpace(_form.ClientId)) { _formError = "Client ID is required."; return; } if (string.IsNullOrWhiteSpace(_form.TenantId)) { _formError = "Tenant ID is required."; return; } if (_editing == null) { _form.Id = Guid.NewGuid().ToString(); _profiles.Add(_form); } else { var idx = _profiles.FindIndex(p => p.Id == _editing.Id); if (idx >= 0) _profiles[idx] = _form; } await ProfileRepo.SaveAsync(_profiles); _showForm = false; _editing = null; } private async Task DeleteProfile(TenantProfile p) { _profiles.RemoveAll(x => x.Id == p.Id); await ProfileRepo.SaveAsync(_profiles); if (Session.CurrentProfile?.Id == p.Id) await Session.ClearSessionAsync(); } }