2025-10-28 13:38:57 +07:00

194 lines
7.4 KiB
C#

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<KratosIntegration> _logger;
public KratosIntegration(IConfiguration config, ILogger<KratosIntegration> 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<dynamic> 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<JsonElement>(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<dynamic> 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<JsonElement>(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<dynamic> 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<JsonElement>(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<dynamic> 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<JsonElement>(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<dynamic> 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<dynamic> 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<KratosSuccessfulNativeRegistration> 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;
}
}