w4c-workflows-api/Services/Credentials/CredentialExtractor.cs
2026-09-12 01:02:46 +03:00

58 lines
2 KiB
C#

using System.Text.Json.Nodes;
using w4c_workflows.Models.Credentials;
namespace w4c_workflows.Services.Credentials;
/// <summary>Tokens pulled out of an OAuth-style token response.</summary>
public sealed record TokenSet(
string AccessToken,
string TokenType,
int? ExpiresInSeconds,
string? RefreshToken,
string? Scope);
/// <summary>
/// Reads a token response using a type's extraction strategy. This is the
/// counterpart to <see cref="CredentialInjector"/>: injection places a token on a
/// request, extraction finds it in a token endpoint's response (dot-paths such as
/// <c>authed_user.access_token</c>). The OAuth refresh flow that consumes this is
/// a later increment; the strategy is modelled and tested now.
/// </summary>
public static class CredentialExtractor
{
public static TokenSet? Extract(JsonObject tokenResponse, CredentialExtraction extraction)
{
var accessToken = ReadPath(tokenResponse, extraction.AccessTokenField);
if (string.IsNullOrEmpty(accessToken))
return null;
var expiresIn = int.TryParse(ReadPath(tokenResponse, extraction.ExpiresInField), out var seconds)
? seconds
: (int?)null;
return new TokenSet(
accessToken,
ReadPath(tokenResponse, extraction.TokenTypeField) ?? extraction.DefaultTokenType,
expiresIn,
ReadPath(tokenResponse, extraction.RefreshTokenField),
ReadPath(tokenResponse, extraction.ScopeField));
}
private static string? ReadPath(JsonObject root, string? path)
{
if (string.IsNullOrWhiteSpace(path))
return null;
JsonNode? current = root;
foreach (var segment in path.Split('.', StringSplitOptions.RemoveEmptyEntries))
{
if (current is not JsonObject obj || !obj.TryGetPropertyValue(segment, out current))
return null;
}
return current is JsonValue value && value.TryGetValue<string>(out var text)
? text
: current?.ToJsonString();
}
}