using System.Text.Json; using Ory.Kratos.Client.Api; using Ory.Kratos.Client.Model; using Ory.Kratos.Client.Client; namespace khmer_eid_backend.Integrations.Ory; public class KratosIntegration { private readonly FrontendApi _frontendApi; private readonly IdentityApi _identityApi; private readonly ILogger _logger; public KratosIntegration(IConfiguration config, ILogger logger) { var publicCfg = new Configuration { BasePath = config["Ory:Kratos:PublicUrl"]! }; var adminCfg = new Configuration { BasePath = config["Ory:Kratos:AdminUrl"]! }; _frontendApi = new FrontendApi(publicCfg); _identityApi = new IdentityApi(adminCfg); _logger = logger; } public async Task CreateOtpRegistrationFlowAsync(string phone) { var flow = await _frontendApi.CreateNativeRegistrationFlowAsync(returnSessionTokenExchangeCode: true); _logger.LogInformation("Started registration flow: {FlowId}", flow.Id); phone = phone.Trim(); var method = new KratosUpdateRegistrationFlowWithCodeMethod( traits: new { phone = phone }, method: "code" ); try { var body = new KratosUpdateRegistrationFlowBody(method); _logger.LogInformation("Updating OTP registration flow for {Phone}, flow {FlowId}", phone, flow.Id); var updatedFlow = _frontendApi.UpdateRegistrationFlow(flow.Id, body); return flow; } catch (ApiException ex) { var res = JsonSerializer.Deserialize(ex.ErrorContent.ToString()!); return ex.ErrorCode switch { 400 => res.GetProperty("state").ToString() == "sent_email" ? new { FlowId = res.GetProperty("id").GetString(), State = res.GetProperty("state").GetString(), ExpiredAt = res.GetProperty("expires_at").GetDateTime() } : throw new Exception("Unhandled Kratos API exception: " + ex.Message), _ => throw new Exception("Unhandled Kratos API exception: " + ex.Message), }; } } public async Task CompleteOtpRegistrationFlowAsync(string flowId, string phone,string otp) { phone = phone.Trim(); var method = new KratosUpdateRegistrationFlowWithCodeMethod( code: otp, traits: new { phone = phone}, method: "code" ); var body = new KratosUpdateRegistrationFlowBody(method); try { var result = await _frontendApi.UpdateRegistrationFlowAsync(flowId, body); _logger.LogInformation("Completed OTP registration flow for flow {FlowId}", flowId); return new { accessToken = result.SessionToken, expiredAt = result.Session?.ExpiresAt }; } catch (ApiException ex) { var res = JsonSerializer.Deserialize(ex.ErrorContent.ToString()!); return ex.ErrorCode switch { 400 => res.GetProperty("ui").GetProperty("messages").EnumerateArray().First().GetProperty("text").GetString()!, 404 => throw new Exception("Registration flow not found."), _ => throw new Exception("Unhandled Kratos API exception: " + ex.Message), }; } } public async Task CreateOtpLoginFlowAsync(string phone) { var flow = await _frontendApi.CreateNativeLoginFlowAsync(returnSessionTokenExchangeCode: true); _logger.LogInformation("Started login flow: {FlowId}", flow.Id); phone = phone.Trim(); var method = new KratosUpdateLoginFlowWithCodeMethod( method: "code", csrfToken: "dfdfdfde", identifier: phone ); try { var body = new KratosUpdateLoginFlowBody(method); _logger.LogInformation("Updating OTP registration flow for {Phone}, flow {FlowId}", phone, flow.Id); var updatedFlow = await _frontendApi.UpdateLoginFlowAsync(flow.Id, body); return flow; } catch (ApiException ex) { var res = JsonSerializer.Deserialize(ex.ErrorContent.ToString()!); return ex.ErrorCode switch { 400 => res.GetProperty("state").ToString() == "sent_email" ? new { FlowId = res.GetProperty("id").GetString(), State = res.GetProperty("state").GetString(), ExpiredAt = res.GetProperty("expires_at").GetDateTime() } : throw new Exception("Unhandled Kratos API exception: " + ex.Message), _ => throw new Exception("Unhandled Kratos API exception: " + ex.Message), }; } } public async Task CompleteOtpLoginFlowAsync(string flowId, string phone, string otp) { phone = "+" + phone.Trim(); var method = new KratosUpdateLoginFlowWithCodeMethod( code: otp, method: "code", csrfToken: "dfdfdfde", identifier: phone ); var body = new KratosUpdateLoginFlowBody(method); try { var result = await _frontendApi.UpdateLoginFlowAsync(flowId, body); _logger.LogInformation("Completed OTP login flow for flow {FlowId}", flowId); return new { accessToken = result.SessionToken, expiredAt = result.Session?.ExpiresAt }; } catch (ApiException ex) { var res = JsonSerializer.Deserialize(ex.ErrorContent.ToString()!); return ex.ErrorCode switch { 400 => res, 404 => throw new Exception("Login flow not found."), _ => throw new Exception("Unhandled Kratos API exception: " + ex.Message), }; } } public async Task Logout(string sessionToken) { var body = new KratosPerformNativeLogoutBody(sessionToken: sessionToken); await _frontendApi.PerformNativeLogoutAsync(body); _logger.LogInformation("Logged out session with token {SessionToken}", sessionToken); return new { Message = "Logged out successfully." }; } public async Task GetMe(string sessionToken) { var session = await _frontendApi.ToSessionAsync(xSessionToken: sessionToken); _logger.LogInformation("Fetched session for identity {IdentityId}", session.Identity.Id); return session; } public async Task PasswordRegistrationFlowAsync(string flowId, string phone) { var method = new KratosUpdateRegistrationFlowWithPasswordMethod( password: "add3ae4d8ae8", traits: new { phone = phone, identifier = phone }, method: "password" ); var body = new KratosUpdateRegistrationFlowBody(method); var result = await _frontendApi.UpdateRegistrationFlowAsync(flowId, body); Console.WriteLine(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true })); _logger.LogInformation("Completed registration flow for {Phone}", phone); return result; } }