using System.Text.Json; using System.Text.Json.Serialization; namespace DysonNetwork.Sphere.Auth.OpenId; /// /// Represents the state parameter used in OpenID Connect flows. /// Handles serialization and deserialization of the state parameter. /// public class OidcState { /// /// The type of OIDC flow (login or connect). /// public OidcFlowType FlowType { get; set; } /// /// The account ID (for connect flow). /// public Guid? AccountId { get; set; } /// /// The OIDC provider name. /// public string? Provider { get; set; } /// /// The nonce for CSRF protection. /// public string? Nonce { get; set; } /// /// The device ID for the authentication request. /// public string? DeviceId { get; set; } /// /// The return URL after authentication (for login flow). /// public string? ReturnUrl { get; set; } /// /// Creates a new OidcState for a connection flow. /// public static OidcState ForConnection(Guid accountId, string provider, string nonce, string? deviceId = null) { return new OidcState { FlowType = OidcFlowType.Connect, AccountId = accountId, Provider = provider, Nonce = nonce, DeviceId = deviceId }; } /// /// Creates a new OidcState for a login flow. /// public static OidcState ForLogin(string returnUrl = "/", string? deviceId = null) { return new OidcState { FlowType = OidcFlowType.Login, ReturnUrl = returnUrl, DeviceId = deviceId }; } /// /// The version of the state format. /// public int Version { get; set; } = 1; /// /// Serializes the state to a JSON string for use in OIDC flows. /// public string Serialize() { return JsonSerializer.Serialize(this, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); } /// /// Attempts to parse a state string into an OidcState object. /// public static bool TryParse(string? stateString, out OidcState? state) { state = null; if (string.IsNullOrEmpty(stateString)) return false; try { // First try to parse as JSON try { state = JsonSerializer.Deserialize(stateString); return state != null; } catch (JsonException) { // Not a JSON string, try legacy format for backward compatibility return TryParseLegacyFormat(stateString, out state); } } catch { return false; } } private static bool TryParseLegacyFormat(string stateString, out OidcState? state) { state = null; var parts = stateString.Split('|'); // Check for connection flow format: {accountId}|{provider}|{nonce}|{deviceId}|connect if (parts.Length >= 5 && Guid.TryParse(parts[0], out var accountId) && string.Equals(parts[^1], "connect", StringComparison.OrdinalIgnoreCase)) { state = new OidcState { FlowType = OidcFlowType.Connect, AccountId = accountId, Provider = parts[1], Nonce = parts[2], DeviceId = parts.Length >= 4 && !string.IsNullOrEmpty(parts[3]) ? parts[3] : null }; return true; } // Check for login flow format: {returnUrl}|{deviceId}|login if (parts.Length >= 2 && parts.Length <= 3 && (parts.Length < 3 || string.Equals(parts[^1], "login", StringComparison.OrdinalIgnoreCase))) { state = new OidcState { FlowType = OidcFlowType.Login, ReturnUrl = parts[0], DeviceId = parts.Length >= 2 && !string.IsNullOrEmpty(parts[1]) ? parts[1] : null }; return true; } // Legacy format support (for backward compatibility) if (parts.Length == 1) { state = new OidcState { FlowType = OidcFlowType.Login, ReturnUrl = parts[0], DeviceId = null }; return true; } return false; } } /// /// Represents the type of OIDC flow. /// public enum OidcFlowType { /// /// Login or registration flow. /// Login, /// /// Account connection flow. /// Connect }