190 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			190 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System.Text.Json;
 | |
| using System.Text.Json.Serialization;
 | |
| 
 | |
| namespace DysonNetwork.Pass.Auth.OpenId;
 | |
| 
 | |
| /// <summary>
 | |
| /// Represents the state parameter used in OpenID Connect flows.
 | |
| /// Handles serialization and deserialization of the state parameter.
 | |
| /// </summary>
 | |
| public class OidcState
 | |
| {
 | |
|     /// <summary>
 | |
|     /// The type of OIDC flow (login or connect).
 | |
|     /// </summary>
 | |
|     public OidcFlowType FlowType { get; set; }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// The account ID (for connect flow).
 | |
|     /// </summary>
 | |
|     public Guid? AccountId { get; set; }
 | |
| 
 | |
| 
 | |
|     /// <summary>
 | |
|     /// The OIDC provider name.
 | |
|     /// </summary>
 | |
|     public string? Provider { get; set; }
 | |
| 
 | |
| 
 | |
|     /// <summary>
 | |
|     /// The nonce for CSRF protection.
 | |
|     /// </summary>
 | |
|     public string? Nonce { get; set; }
 | |
| 
 | |
| 
 | |
|     /// <summary>
 | |
|     /// The device ID for the authentication request.
 | |
|     /// </summary>
 | |
|     public string? DeviceId { get; set; }
 | |
| 
 | |
| 
 | |
|     /// <summary>
 | |
|     /// The return URL after authentication (for login flow).
 | |
|     /// </summary>
 | |
|     public string? ReturnUrl { get; set; }
 | |
| 
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Creates a new OidcState for a connection flow.
 | |
|     /// </summary>
 | |
|     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
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Creates a new OidcState for a login flow.
 | |
|     /// </summary>
 | |
|     public static OidcState ForLogin(string returnUrl = "/", string? deviceId = null)
 | |
|     {
 | |
|         return new OidcState
 | |
|         {
 | |
|             FlowType = OidcFlowType.Login,
 | |
|             ReturnUrl = returnUrl,
 | |
|             DeviceId = deviceId
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// The version of the state format.
 | |
|     /// </summary>
 | |
|     public int Version { get; set; } = 1;
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Serializes the state to a JSON string for use in OIDC flows.
 | |
|     /// </summary>
 | |
|     public string Serialize()
 | |
|     {
 | |
|         return JsonSerializer.Serialize(this, new JsonSerializerOptions
 | |
|         {
 | |
|             NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
 | |
|             PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
 | |
|             DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Attempts to parse a state string into an OidcState object.
 | |
|     /// </summary>
 | |
|     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<OidcState>(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;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// <summary>
 | |
| /// Represents the type of OIDC flow.
 | |
| /// </summary>
 | |
| public enum OidcFlowType
 | |
| {
 | |
|     /// <summary>
 | |
|     /// Login or registration flow.
 | |
|     /// </summary>
 | |
|     Login,
 | |
| 
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Account connection flow.
 | |
|     /// </summary>
 | |
|     Connect
 | |
| } |