Standard technicians (TechN0/TechN1) are no longer auto-prompted for a
delegated SharePoint sign-in when selecting a profile — only admins are.
Techs operate under the profile's app (certificate) identity, so a profile
selection never forces them to authenticate.
To keep that usable, the admin profile list now shows a "No shared access"
badge on any profile that isn't certificate-configured, since standard
techs can't operate against those until an admin registers a cert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Feature work:
- Certificate (app-only) auth per profile: cert store, context/Graph client
factories, automated app-registration provisioning (delegated + application
permissions, admin consent), and a SessionManager seam that resolves the auth
model per profile.
- Scheduled reports: repositories, hosted service/runner/coordinator, report
pages, and email delivery (app-only Mail.Send).
- Tenant-wide user-access audit when no site is selected.
Audit fixes:
- Site enumeration: app-only discovery used Graph getAllSites (needs Graph
Sites.Read.All the cert app lacks) and silently returned empty. Switched to
the admin-host CSOM TenantSiteEnumerator, matching the scheduler; both auth
models now share one enumeration path.
- Group expansion: the scan records a SharePoint group as a single principal, so
user-centric audits found nothing for group-granted access. Resolve group
membership (shared by audit + scheduler) and attribute it to the target user.
- M365 group claims: the resolver only recognized AAD security groups
(c:0t.c|). Group-connected/Teams sites grant via the M365 group claim
(c:0o.c|…|<guid>[_o]); now expanded too, resolving owners for the "_o" claim.
- Provision Directory.Read.All as an application permission so M365/AAD group
expansion works under the cert identity.
Also: ignore data/appcerts/ (encrypted certificate key material).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Report branding (top-left MSP logo, top-right client logo):
- Add MspLogo to AppSettings; client logo already on TenantProfile
- IUserSessionService.CurrentBranding composes MSP + active profile logo
- New reusable LogoUpload component (InputFile -> base64 LogoData, 512KB cap)
- MSP logo upload in Settings; optional client logo in profile create/edit
- Wire ReportBranding into all 6 HTML export pages
- Fix EditProfile dropping ClientLogo on edit
Storage metrics: expose folder scan depth (0-20) in scan options UI,
passed to existing StorageScanOptions.FolderDepth recursion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AADSTS700016 came from the register flow sending the configured
Oidc:ClientId (still a placeholder) as the auth client. The desktop
reference app never needs config: it bootstraps with the first-party
"Microsoft Graph Command Line Tools" public client (14d82eec-...) via
MSAL interactive, which exists in every tenant.
Replicate that for the web app. A server can't do MSAL loopback and the
bootstrap client's redirect URIs don't include /connect/callback, so use
the OAuth 2.0 device authorization grant instead — the web-equivalent of
the desktop interactive flow:
- Add EntraDeviceCodeFlow: POST /devicecode then poll /token with the
bootstrap client. No backing app, no client id/secret, no redirect URI.
- Profiles "Register in Entra" now shows the verification URL + user code
and polls until the admin signs in, then calls AppRegistrationService
to create the per-client app and adopts its appId.
- Remove the dead /connect/register-initiate endpoint and the
IsRegistration branch from the callback (connect flow only now).
The client-tenant register/connect flows are now fully secretless. The
Oidc:* config is used only by the toolbox's own sign-in (unchanged).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>