using StellaOps.Auth.Client;
using StellaOps.Cli.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Cli.Extensions;
///
/// Extension methods for IStellaOpsTokenClient providing compatibility with older CLI patterns.
/// These bridge the gap between the old API (GetTokenAsync, GetAccessTokenAsync) and the
/// new API (RequestClientCredentialsTokenAsync, GetCachedTokenAsync).
///
public static class StellaOpsTokenClientExtensions
{
///
/// Requests an access token using client credentials flow with the specified scopes.
/// This is a compatibility shim for the old GetAccessTokenAsync pattern.
///
public static async Task GetAccessTokenAsync(
this IStellaOpsTokenClient client,
IEnumerable scopes,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(client);
var scope = scopes is not null ? string.Join(" ", scopes.Where(s => !string.IsNullOrWhiteSpace(s))) : null;
return await client.RequestClientCredentialsTokenAsync(scope, null, cancellationToken).ConfigureAwait(false);
}
///
/// Requests an access token using client credentials flow with a single scope.
///
public static async Task GetAccessTokenAsync(
this IStellaOpsTokenClient client,
string scope,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(client);
return await client.RequestClientCredentialsTokenAsync(scope, null, cancellationToken).ConfigureAwait(false);
}
///
/// Gets a cached access token or requests a new one if not cached or expired.
/// This is a compatibility shim for the old GetCachedAccessTokenAsync pattern.
/// Cache key includes effective tenant to prevent cross-tenant cache collisions.
///
public static async Task GetCachedAccessTokenAsync(
this IStellaOpsTokenClient client,
IEnumerable scopes,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(client);
var scopeList = scopes?.Where(s => !string.IsNullOrWhiteSpace(s)).OrderBy(s => s).ToArray() ?? [];
var scope = string.Join(" ", scopeList);
var cacheKey = BuildCacheKey(scope);
// Check cache first
var cached = await client.GetCachedTokenAsync(cacheKey, cancellationToken).ConfigureAwait(false);
if (cached is not null && !cached.IsExpired(TimeProvider.System, TimeSpan.FromMinutes(1)))
{
return cached;
}
// Request new token
var result = await client.RequestClientCredentialsTokenAsync(scope, null, cancellationToken).ConfigureAwait(false);
var entry = result.ToCacheEntry();
// Cache the result
await client.CacheTokenAsync(cacheKey, entry, cancellationToken).ConfigureAwait(false);
return entry;
}
///
/// Gets a cached access token or requests a new one if not cached or expired.
/// Single scope version.
///
public static async Task GetCachedAccessTokenAsync(
this IStellaOpsTokenClient client,
string scope,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(client);
var cacheKey = BuildCacheKey(scope ?? "default");
// Check cache first
var cached = await client.GetCachedTokenAsync(cacheKey, cancellationToken).ConfigureAwait(false);
if (cached is not null && !cached.IsExpired(TimeProvider.System, TimeSpan.FromMinutes(1)))
{
return cached;
}
// Request new token
var result = await client.RequestClientCredentialsTokenAsync(scope, null, cancellationToken).ConfigureAwait(false);
var entry = result.ToCacheEntry();
// Cache the result
await client.CacheTokenAsync(cacheKey, entry, cancellationToken).ConfigureAwait(false);
return entry;
}
///
/// Requests a token using client credentials. Parameterless version for simple cases.
///
public static async Task GetTokenAsync(
this IStellaOpsTokenClient client,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(client);
return await client.RequestClientCredentialsTokenAsync(null, null, cancellationToken).ConfigureAwait(false);
}
///
/// Builds a cache key that includes the effective tenant to prevent cross-tenant
/// token cache collisions when users switch tenants between CLI invocations.
///
private static string BuildCacheKey(string scope)
{
var tenant = TenantProfileStore.GetEffectiveTenant(null) ?? "none";
return $"cc:{tenant}:{scope}";
}
}